sync SVN r18077
This commit is contained in:
222
docs/cercetare/audit_vanzari_creare_modificare_stergere.md
Normal file
222
docs/cercetare/audit_vanzari_creare_modificare_stergere.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# Cercetare: coloane de audit pe VANZARI / VANZARI_DETALII (creare/modificare/stergere)
|
||||
|
||||
Status: COMPLET (read-only)
|
||||
|
||||
## Intrebare
|
||||
|
||||
Marius cere pentru audit, pe documentele de facturare: data crearii, utilizatorul crearii, data
|
||||
modificarii si utilizatorul modificarii (daca e cazul), data stergerii si utilizatorul stergerii
|
||||
(daca e cazul).
|
||||
|
||||
## 1. Ce exista deja (structura DB)
|
||||
|
||||
Interogat direct `all_tab_columns` pe `ROA_CENTRAL` (schema live).
|
||||
|
||||
**VANZARI** — coloane relevante pentru audit:
|
||||
|
||||
| Coloana | Tip | Nullable | Rol |
|
||||
|---|---|---|---|
|
||||
| `ID_UTIL` | NUMBER | NOT NULL | utilizatorul crearii — completat la fiecare INSERT |
|
||||
| `DATAORA` | DATE | NOT NULL | data/ora crearii — completat la fiecare INSERT |
|
||||
| `STERS` | NUMBER | NOT NULL | flag sters (0/1) |
|
||||
| `ID_UTILS` | NUMBER | NULL | utilizatorul care a sters — completat doar la stergere |
|
||||
| `DATAORAS` | DATE | NULL | data/ora stergerii — completata doar la stergere |
|
||||
| `DATA_ACT` | DATE | NULL | **data contabila/de inregistrare** (propaga in `ACT`, `DOCUMENTE`, `JV2007`, `RUL`), NU e "data modificarii" — vezi sectiunea 2 |
|
||||
| `DATA_FACTURAT`, `ID_UTILFACT` | DATE / NUMBER | NULL | specifice actiunii "facturare din aviz", nu audit general pe document |
|
||||
| `DATAORA_EXP` | DATE | NOT NULL | data expedierii/listarii, nu e audit de scriere |
|
||||
| `DATAORA_DESCARCAT` | DATE | NULL | data descarcarii gestiunii, nu e audit de scriere |
|
||||
| `DATA_SCAD` | DATE | NULL | data scadenta, nimic de audit |
|
||||
|
||||
**Nu exista nicio coloana dedicata "utilizator modificare" / "data modificare"** pe `VANZARI`
|
||||
(gen `ID_UTIL_MODIF` / `DATA_MODIF`). `DATA_ACT` a fost verificata explicit in cod
|
||||
(`EXPORT:14439-14500`, `modifica_date_factura`) si e o data contabila propagata in `ACT`/`DOCUMENTE`/
|
||||
`JV2007`/`RUL`, nu un marcaj de audit "cine/cand a modificat".
|
||||
|
||||
**Conventia casei (adaugat, verificat de sesiunea principala):** `VANZARI` are deja tiparul **o
|
||||
pereche utilizator+data per eveniment**: `ID_UTIL`/`DATAORA` (creare), `ID_UTILS`/`DATAORAS`
|
||||
(stergere), `ID_UTILFACT`/`DATA_FACTURAT` (facturare din aviz — a treia pereche, omisa din
|
||||
inventarul initial). Cu aceasta a treia pereche vizibila, tiparul e limpede: o pereche noua de
|
||||
"modificare" (`ID_UTIL_MODIF`/`DATA_MODIF` sau echivalent) s-ar aseza natural langa celelalte trei,
|
||||
ca nume si ca forma — nu ar fi o conventie noua, ci continuarea uneia deja existente.
|
||||
|
||||
**VANZARI_DETALII** — coloane relevante:
|
||||
|
||||
| Coloana | Tip | Nullable | Rol |
|
||||
|---|---|---|---|
|
||||
| `VALIDAT`, `ID_UTIL_VALID`, `DATAORA_VALID` | NUMBER/NUMBER/DATE | NULL pe ultimele doua | validare, alt concept decat creare |
|
||||
| `STERS`, `ID_UTILS`, `DATAORAS` | NUMBER/NUMBER/DATE | NULL pe ultimele doua | stergere linie |
|
||||
| `DATAORA_DESCARCAT` | DATE | NULL | descarcare gestiune |
|
||||
|
||||
**Pe `VANZARI_DETALII` nu exista nicio coloana de "creare"** (nici `ID_UTIL`, nici `DATAORA` proprii
|
||||
liniei) — creatorul/data liniei se deduce indirect din antetul `VANZARI` al documentului parinte.
|
||||
|
||||
## 2. Cine scrie coloanele si cand
|
||||
|
||||
**Creare (`ID_UTIL`, `DATAORA` pe VANZARI):** scrise o singura data, la `INSERT INTO VANZARI`
|
||||
din `PACK_FACTURARE.scrie_in_vanzari` (`EXPORT:13598-13640`), apelata pe drumul principal de emitere
|
||||
(`scrie_factura2`, `EXPORT:6020` -> lantul de `finalizeaza_*`). Valorile vin din
|
||||
`pack_facturare.nid_util` si `V_DATAORA` — utilizatorul si momentul sesiunii curente la INSERT.
|
||||
Exista un al doilea `INSERT INTO VANZARI` (`EXPORT:14930`, in `finalizeaza_avize_lucrare`,
|
||||
`EXPORT:14854-...`) — ruta specifica avizelor de lucrare, tot cu `ID_UTIL`/`DATAORA` completate la
|
||||
INSERT. **Ambele rute de emitere completeaza consecvent aceste doua coloane** — nu s-a gasit niciun
|
||||
INSERT in VANZARI care sa le lase NULL.
|
||||
|
||||
**Nu exista niciun `UPDATE VANZARI SET ID_UTIL = ...` sau `SET DATAORA = ...` in tot pachetul**
|
||||
(cautare directa, zero rezultate) — deci, in afara de INSERT-ul initial, aceste doua coloane nu sunt
|
||||
niciodata rescrise pe randul existent. Coerent cu design-ul de azi: singura cale de "modificare" a
|
||||
antetului identitar e `modifica_date_factura` (care NU atinge `ID_UTIL`/`DATAORA`, doar serie/numar/
|
||||
data/scadenta/delegat/etc., vezi `plan_13...md:796-801`), sau stergere+reemitere ca document nou.
|
||||
|
||||
**Stergere (`ID_UTILS`, `DATAORAS`, `STERS`):** scrise consecvent in ambele proceduri de stergere:
|
||||
- `sterge_factura` (`EXPORT:5432-5607`): `UPDATE VANZARI SET STERS = V_STERS, ID_UTILS = V_ID_UTIL,
|
||||
DATAORAS = V_DATAORA WHERE ID_VANZARE = ...` (`EXPORT:5496-5499`) si acelasi tipar pe
|
||||
`VANZARI_DETALII` (`EXPORT:5560-5564`), pe `COMENZI_ELEMENTE` si `CTR_RATE_FACTURI` cand e cazul.
|
||||
- `sterge_proforma` (`EXPORT:5610-...`): acelasi tipar (verificat header-ul procedurii; corpul
|
||||
complet urmeaza acelasi model de `UPDATE ... SET STERS/ID_UTILS/DATAORAS`).
|
||||
|
||||
**Concluzie punct 2: coloanele existente sunt scrise consecvent** pe toate rutele identificate —
|
||||
nu exista ruta de creare care sa lase `ID_UTIL`/`DATAORA` NULL, nici ruta de stergere care sa sara
|
||||
peste `ID_UTILS`/`DATAORAS`.
|
||||
|
||||
**CORECTIE (verificata de sesiunea principala, nu de mine): afirmatia initiala "#6 nu atinge niciun
|
||||
camp de audit" era gresita pentru `VANZARI_DETALII`.** Pe partea de nota contabila
|
||||
(`pack_contafin.finalizeaza_modificare_nota`, apelat din `oscrie_in_fisiere`) ramane adevarat ca se
|
||||
**realiniaza doar `VANZARI.COD`** prin `actualizeaza_vanzari`
|
||||
(`COMUN\docs\flux-modificare-stergere-nota-jurnal.md:95`), iar `VANZARI.ID_FACT` "nu e atins de
|
||||
niciun pas al secventei" (`:101-102`) — **dar** editarea liniilor de factura din #6
|
||||
(`COMUN\programe\ofacturare_editare.prg`) **scrie** `id_utils`/`dataoras` pe `VANZARI_DETALII`, in
|
||||
trei locuri:
|
||||
- `ofacturare_editare.prg:492-494` — marcarea unei linii ca stearsa: `sters = 1` impreuna cu
|
||||
`id_utils`/`dataoras`. Aici folosirea corespunde exact semanticii "stergere".
|
||||
- `ofacturare_editare.prg:501-505` — `UPDATE vanzari_detalii SET sters = 0, cantitate = ...,
|
||||
pret = ..., id_utils = ..., dataoras = sysdate` — perechea de "stergere" e scrisa **odata cu
|
||||
`sters = 0`**, deci pe un rand **viu**, nesters.
|
||||
- `ofacturare_editare.prg:522-525` — `INSERT INTO vanzari_detalii (..., id_utils, dataoras)
|
||||
VALUES (...)` — perechea e populata pe un rand **proaspat inserat**, care nu a fost sters
|
||||
niciodata.
|
||||
|
||||
**Concluzia corecta:** in codul livrat al lui #6, perechea `ID_UTILS`/`DATAORAS` pe
|
||||
`VANZARI_DETALII` e folosita ca marcaj **"cine a umblat ultima data pe linia asta"**, nu strict ca
|
||||
"sters de/la". **Consecinta pentru orice raport de audit:** `ID_UTILS`/`DATAORAS` NU pot fi citite
|
||||
ca "sters de/la" fara sa se puna si `STERS` in conditie — altfel liniile adaugate sau modificate la
|
||||
o editare (nesterse) apar gresit drept sterse. Pe `VANZARI` (antet), ramane adevarat ca #6 atinge
|
||||
doar `COD` — nu s-a gasit nicio scriere pe `ID_UTIL`, `DATAORA`, `DATA_ACT`, `ID_UTILS` sau
|
||||
`DATAORAS` la nivel de antet in acest flux.
|
||||
|
||||
## 3. Ce se intampla la regenerare (stergere + reemitere, #13 / S9)
|
||||
|
||||
**Important: acest mecanism NU e inca implementat.** Descrierea "stergere + reemitere" e planul
|
||||
#13, sectiunea S9 (`docs\plan_13_unificare_formular_facturare.md:3484-3568`), marcata "PROIECTAT" /
|
||||
"VERIFICAT ca e realizabila", nu cod livrat. Feature-ul aflat azi in lucru pe branch-ul curent (#6)
|
||||
e alt mecanism (editare la nivel de linie de nota, sectiunea 2 mai sus), nu regenerare.
|
||||
|
||||
**Raspuns la intrebarea critica: DA, se pierde, exact cum ai suspectat.**
|
||||
|
||||
Mecanismul S9, asa cum e proiectat: documentul vechi primeste soft-delete (`sterge_factura`, ca
|
||||
azi) -> `ID_UTILS`/`DATAORAS` ale randului **vechi** devin utilizatorul/momentul editarii (corect,
|
||||
asta chiar e semantica lor). Documentul nou se scrie **pe acelasi drum de emitere ca la creare**
|
||||
(`scrie_factura2` -> `scrie_in_vanzari` -> `INSERT INTO VANZARI`, sectiunea 2 de mai sus) — acelasi
|
||||
`INSERT` care completeaza `ID_UTIL`/`DATAORA` din utilizatorul si momentul curente. **Niciun pas din
|
||||
S9 nu citeste sau transporta `ID_UTIL`/`DATAORA` ale documentului vechi catre cel nou** — cautare
|
||||
directa in tot planul (`ID_UTIL `, `DATAORA `) nu gaseste nicio mentiune a preservarii lor la
|
||||
regenerare. Deci, cu proiectarea de azi a S9: **"data crearii" a documentului reemis devine data
|
||||
regenerarii, iar "utilizatorul crearii" devine cel care a declansat editarea** — informatia despre
|
||||
cine/cand a fost creat *initial* documentul se pierde tacut, exact temerea din brief.
|
||||
|
||||
**Atenuare (verificata de sesiunea principala):** pierderea nu e totala, ci **reconstruibila din
|
||||
lant, nu direct pe document**. Randul vechi ramane in tabel cu `STERS = 1` si cu `ID_UTILS`/
|
||||
`DATAORAS` completate — iar acea stergere **este** momentul modificarii. Cum `ID_FACT` se pastreaza
|
||||
peste regenerare (sectiunea E/S9 mai jos), un raport de audit poate urca lantul `ID_FACT` -> gasi
|
||||
randul vechi sters -> citi `DATAORA`/`ID_UTIL` de pe acela ca fiind "data/utilizator crearii
|
||||
originale". Asta atenueaza, dar nu inlocuieste o pereche explicita: cere parcurgerea lantului de
|
||||
randuri sterse in loc de o citire directa pe documentul curent, si se rupe daca vreodata `ID_FACT`
|
||||
nu mai e pastrat identic (de exemplu la o a doua regenerare, daca lantul nu ramane liniar).
|
||||
|
||||
**Precedentul `ID_FACT` exista si e citat corect in brief, si arata ca problema e cunoscuta ca tipar
|
||||
— dar rezolvata doar pentru `ID_FACT`, nu si generalizata la audit.** Planul dedica un mecanism
|
||||
explicit ca sa evite pierderea lui `ID_FACT`:
|
||||
- Sectiunea E (`:762-789`): cerinta explicita ("documentul reemis pastreaza `ID_FACT`"), verificarea
|
||||
ca azi secventa l-ar regenera necondiționat, si decizia sa fie **citit din documentul vechi
|
||||
inainte de stergere si impus** celui nou.
|
||||
- S9 (`:3505-3538`): mecanismul concret — "`ID_FACT` se citeste inainte de stergere ... si se impune
|
||||
documentului nou, printr-un comutator folosit numai de regenerare"; plus tot efortul de a ocoli
|
||||
coliziunea `ORA-00001` pe `PK_DOCUMENTE` cand se refoloseste acelasi `ID_FACT`.
|
||||
|
||||
Acelasi tipar de mecanism ("citeste din vechi inainte de stergere, transporta explicit la INSERT-ul
|
||||
nou") ar fi necesar si pentru `ID_UTIL`/`DATAORA` daca se vrea pastrata "data/utilizator creare
|
||||
originala" — **dar acest pas nu exista nicaieri in plan azi**. Nu e o eroare de implementare, e un
|
||||
gol de cerinta: planul #13 nu a fost scris cu "pastreaza si audit-ul de creare" ca obiectiv:
|
||||
sectiunea 1 a acestui raport (`plan_13...md:1462-1467`) chiar **foloseste** `ID_UTILS`/`DATAORAS`
|
||||
NULL ca dovada ca "nicio editare ulterioara nu s-a inregistrat" pe o factura din 2026 — ceea ce arata
|
||||
ca autorii planului tratau deja `ID_UTILS`/`DATAORAS` (stergere) ca semnal indirect de "a fost
|
||||
editat", dar fara sa discute explicit soarta lui `ID_UTIL`/`DATAORA` (creare) la regenerare.
|
||||
|
||||
## 4. Ce lipseste din cele sase cerute de Marius
|
||||
|
||||
| Cerut | Exista azi? | Observatie |
|
||||
|---|---|---|
|
||||
| Data crearii | DA — `VANZARI.DATAORA` | Scrisa consecvent la INSERT (sectiunea 2). Sub #13/S9 asa cum e proiectat azi, **s-ar suprascrie tacit la fiecare regenerare** (sectiunea 3) — nimic nu o transporta din documentul vechi. |
|
||||
| Utilizatorul crearii | DA — `VANZARI.ID_UTIL` | Idem: scris consecvent la INSERT, dar **s-ar pierde la regenerare** sub #13/S9 asa cum e proiectat azi. |
|
||||
| Data modificarii (daca e cazul) | **NU exista coloana dedicata pe antet (`VANZARI`)** | Nu exista `DATA_MODIF`/echivalent. `DATA_ACT` exista dar e data contabila, nu audit. Pe `VANZARI` (antet), #6 nu scrie nimic. **Pe `VANZARI_DETALII` (linie), #6 scrie `dataoras` chiar si pe randuri nesterse** (`ofacturare_editare.prg:501-505,522-525`) — semnal de "ultima atingere", dar suprapus peste semantica de stergere, nu o coloana proprie de modificare. Sub #13/S9, singurul semnal indirect pe antet ar fi `DATAORAS` a randului **vechi** (marcat sters) — reconstruibil prin lant (vezi atenuarea din sectiunea 3), nu direct pe documentul curent. |
|
||||
| Utilizatorul modificarii (daca e cazul) | **NU exista coloana dedicata pe antet (`VANZARI`)** | Acelasi rationament — nu exista `ID_UTIL_MODIF`. Pe `VANZARI_DETALII`, #6 scrie `id_utils` si pe randuri nesterse (acelasi loc de mai sus), cu aceeasi suprapunere peste semantica de stergere. Pe antet, #13/S9 ar lasa doar `ID_UTILS` pe randul vechi (sters), reconstruibil prin lant, nu pe cel curent. |
|
||||
| Data stergerii | DA — `VANZARI.DATAORAS` | Scrisa consecvent in `sterge_factura` si `sterge_proforma` (sectiunea 2). |
|
||||
| Utilizatorul stergerii | DA — `VANZARI.ID_UTILS` | Idem, scris consecvent. |
|
||||
|
||||
**Rezumat:** 4 din 6 cerinte au deja coloana dedicata si scriere consecventa pe antet (creare x2,
|
||||
stergere x2) — dar cele doua de "creare" sunt **fragile fata de regenerarea planificata in #13**,
|
||||
riscand sa fie suprascrise silentios daca S9 nu adauga un pas explicit de transport (dupa modelul
|
||||
deja folosit pentru `ID_FACT`), atenuat de faptul ca raman reconstruibile prin lant (sectiunea 3).
|
||||
Cele doua de "modificare" **nu au coloana proprie pe antet**: pe `VANZARI` nici azi (#6 atinge doar
|
||||
`COD`), nici in proiectarea #13 (regenerarea confunda "modificare" cu "creare noua" + "stergere
|
||||
veche"); pe `VANZARI_DETALII`, #6 **reutilizeaza** `ID_UTILS`/`DATAORAS` ca semnal de "ultima
|
||||
atingere" chiar pe linii nesterse — util ca indiciu, dar ambiguu fara `STERS` in conditie, si tot nu
|
||||
e o pereche explicita de "modificare" pe care un raport sa o citeasca direct fara ambiguitate.
|
||||
|
||||
## Verificat direct vs dedus vs neacoperit
|
||||
|
||||
**Nota de provenienta:** sectiunile 1-2 si structura raportului sunt cercetarea mea initiala.
|
||||
Corectia despre `ofacturare_editare.prg:492-494,501-505,522-525` (sectiunea 2, editarea #6 pe
|
||||
`VANZARI_DETALII`), perechea `ID_UTILFACT`/`DATA_FACTURAT` (sectiunea 1) si atenuarea prin lant
|
||||
`ID_FACT` (sectiunea 3) **au fost verificate si furnizate de sesiunea principala**, nu de mine — le-am
|
||||
integrat ca atare, marcate explicit in text la locul lor.
|
||||
|
||||
**Verificat direct (citit in cod / rulat pe DB):**
|
||||
- Structura `all_tab_columns` pentru `VANZARI` si `VANZARI_DETALII` (interogare live pe
|
||||
`ROA_CENTRAL`, sectiunea 1) — a mea; reconfirmata independent de sesiunea principala, inclusiv
|
||||
filtrarea explicita pe `%MODIF%` (zero rezultate).
|
||||
- `INSERT INTO VANZARI` din `scrie_in_vanzari` (`EXPORT:13598-13640`) si al doilea din
|
||||
`finalizeaza_avize_lucrare` (`EXPORT:14930`) — sursa lui `ID_UTIL`/`DATAORA` — a mea.
|
||||
- Zero rezultate la cautarea `UPDATE VANZARI SET ... ID_UTIL/DATAORA` in tot pachetul — confirmat
|
||||
prin grep direct pe fisierul export — a mea.
|
||||
- `sterge_factura` complet (`EXPORT:5432-5607`) — scrierea `STERS`/`ID_UTILS`/`DATAORAS` pe
|
||||
`VANZARI` (`:5496-5499`) si `VANZARI_DETALII` (`:5560-5564`) — a mea.
|
||||
- `modifica_date_factura` (`EXPORT:14439-14500`) — confirmat ca `DATA_ACT` e propagata catre
|
||||
`ACT`/`DOCUMENTE`/`JV2007`/`RUL`, deci e data contabila, nu audit — a mea.
|
||||
- `COMUN\docs\flux-modificare-stergere-nota-jurnal.md:95,101-102` — editarea #6 (nota contabila)
|
||||
atinge doar `VANZARI.COD`, nu `ID_FACT`, si nu s-a gasit nicio scriere pe coloanele de audit **de
|
||||
antet** in acel flux — a mea, ramane corecta doar pentru `VANZARI`, nu pentru `VANZARI_DETALII`.
|
||||
- `ofacturare_editare.prg:492-494, 501-505, 522-525` — scrierea `id_utils`/`dataoras` pe
|
||||
`VANZARI_DETALII`, inclusiv pe randuri nesterse — **a sesiunii principale**, eu nu am citit acest
|
||||
fisier (nu era in perimetrul cercetarii initiale, care s-a concentrat pe pachetul PL/SQL).
|
||||
- Sectiunile E si S9 din `docs\plan_13_unificare_formular_facturare.md` (mecanismul de pastrare a
|
||||
`ID_FACT`) si absenta oricarei mentiuni `ID_UTIL`/`DATAORA` in tot documentul (cautare directa,
|
||||
singurele hit-uri sunt in alt context, sectiunea 1 din acest raport) — a mea.
|
||||
|
||||
**Dedus (nu verificat direct, dar sustinut de dovezile de mai sus):**
|
||||
- Ca S9, DACA se implementeaza exact cum e proiectat azi in plan, ar suprascrie `ID_UTIL`/`DATAORA`
|
||||
la regenerare — dedus din faptul ca reemiterea foloseste acelasi `INSERT INTO VANZARI` ca emiterea
|
||||
normala, si niciun pas de transport nu e mentionat in plan. Nu exista inca implementare de rulat.
|
||||
- `sterge_proforma` (`EXPORT:5610-...`) urmeaza acelasi tipar ca `sterge_factura` — verificat doar
|
||||
header-ul si inceputul; nu am citit tot corpul procedurii linie cu linie (structura generala insa
|
||||
se potriveste, fiind aceeasi familie de proceduri din acelasi pachet).
|
||||
|
||||
**Neacoperit:**
|
||||
- Nu am verificat daca exista si alte cai de INSERT/UPDATE pe `VANZARI` in afara `PACK_FACTURARE`
|
||||
(de exemplu `PACK_MIGRARE`, migrari istorice) care ar putea lasa `ID_UTIL`/`DATAORA` NULL sau
|
||||
cu alta semantica pe date vechi — nu era in scopul intrebarii (audit pe fluxul curent).
|
||||
`plan_13...md:844-847` mentioneaza ca a existat deja o cautare exhaustiva pe toate `UPDATE
|
||||
VANZARI` (13 aparitii) in runda 7, dar cu alt scop (antet, nu audit) — nu am reluat-o eu.
|
||||
- Nu am verificat daca exista rapoarte/ecrane in aplicatie care deja afiseaza vreuna din aceste
|
||||
coloane catre utilizator (relevant pentru UX, nu pentru intrebarea de audit DB pusa aici).
|
||||
- Nu am rulat interogari pe date reale (cate facturi au `ID_UTILS`/`DATAORAS` populate azi in
|
||||
productie) — brief-ul cerea structura si rutele de scriere, nu statistici.
|
||||
146
docs/cercetare/buton_comutator_picture.md
Normal file
146
docs/cercetare/buton_comutator_picture.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# Cercetare: buton comutator creion/discheta (decizia 9, plan #13)
|
||||
|
||||
Verificat pe cod la 09.08.2026. Metoda: `vfp_symbols.ps1` (`-Class`, `-Find`, `-Where`, `-Grep -CodeOnly`)
|
||||
peste indexul ROAFACTURARE, plus `Grep`/`Read` directe pe `.vc2`.
|
||||
|
||||
## 1. Clase de buton in `COMUN\clase\cmd_butoane.vc2` — salvare / modificare
|
||||
|
||||
Fisierul e o insiruire de `DEFINE CLASS ... AS buton OF "_cmd_base.vcx"` (butoane simple) si
|
||||
`... AS cmd_buton OF "_cmd_base.vcx"` (variante). Doar doua clase au legatura directa:
|
||||
|
||||
- **`but_modifica`** — `cmd_butoane.vc2:184-198`
|
||||
```
|
||||
184: DEFINE CLASS but_modifica AS buton OF "_cmd_base.vcx"
|
||||
188: caction = inainte_de_do_modifica
|
||||
189: cpicturedown = modific_jos.bmp
|
||||
190: cpictureup = modific_sus.bmp
|
||||
193: Picture = ..\grafice\modific_sus.bmp
|
||||
194: ToolTipText = "Modificare (CTRL+M)"
|
||||
195: Visible = .F.
|
||||
```
|
||||
- **`but_salveaza`** — `cmd_butoane.vc2:340-352`
|
||||
```
|
||||
340: DEFINE CLASS but_salveaza AS buton OF "_cmd_base.vcx"
|
||||
344: caction = do_salvare
|
||||
345: cpicturedown = save_jos.bmp
|
||||
346: cpictureup = save_sus.bmp
|
||||
348: Picture = ..\grafice\save_sus.bmp
|
||||
349: ToolTipText = "Salvare"
|
||||
```
|
||||
|
||||
Nu exista o clasa `but_salvare` (fara "ea") — numele real e `but_salveaza`. Nicio alta clasa din
|
||||
fisier (`but_reset`, `but_reface`, `but_retur`, `cmd_modifica` etc.) foloseste imagini de
|
||||
discheta sau de creion.
|
||||
|
||||
## 2. Clasa efectiva a lui `but_modifica` in formularele de facturare
|
||||
|
||||
Cautat in `COMUN\clase\ofacturare_comun.vc2`, `COMUN\clase\ofacturare.vc2`,
|
||||
`Clase\ofundal_facturare.vc2` (`ofacturare_comun.vc2` e cel corect; `Clase\ofacturare.vc2` din
|
||||
prompt nu exista — clasa reala e in `COMUN\clase\ofacturare.vc2`).
|
||||
|
||||
- `ofacturare_comun.vc2:1424-1432` — `frm_facturi` (lista de facturi), `ADD OBJECT 'but_modifica1'
|
||||
AS but_modifica WITH ... Picture = ..\grafice\modific_sus.bmp` (override explicit, egal cu
|
||||
default-ul clasei). `caction` nu e suprascris pe instanta -> ramane `inainte_de_do_modifica` din
|
||||
clasa. Metoda `inainte_de_do_modifica` (referita si in plan la J) e la
|
||||
`ofacturare_comun.vc2:4925-4934` si azi deschide un `xmenu()`, nu comuta imaginea.
|
||||
- `ofacturare_comun.vc2:1434-1443` — `But_modifica2` pe acelasi `frm_facturi`, tot `AS but_modifica`,
|
||||
cu `caction = do_modifica_explicatie`; fara `Picture` propriu (mosteneste creionul).
|
||||
- `COMUN\clase\ofacturare.vc2:4313, 11192, 19472, 22734` — patru instante `ADD OBJECT
|
||||
'But_modifica1' AS but_modifica`, apartinand `frm_articole_compuse` (`:4212-5216`),
|
||||
`frm_facturare_articole` (`:10968-15739` — formularul de compunere de azi), `frm_nomrute`
|
||||
(`:19357-19551`) si `frm_rute` (`:22510-22821`). Niciuna nu are `Picture` pe instanta.
|
||||
- **Nicio instanta `but_salveaza` / `but_salvare` gasita** in niciunul din cele trei fisiere
|
||||
cautate. Formularele de facturare de azi nu au un buton dedicat de "salvare antet" — antetul se
|
||||
salveaza azi prin `frm_modifica_factura` cu bife (`ofacturare_comun.vc2:5262-5774`, vezi si
|
||||
planul, sectiunea I), nu printr-un buton `but_salveaza` separat.
|
||||
|
||||
Concluzie pe punctul 2: `but_modifica1`/`But_modifica2` de pe `frm_facturi` si toate cele patru
|
||||
`But_modifica1` din `ofacturare.vc2` sunt instante ale clasei `but_modifica` din
|
||||
`cmd_butoane.vc2`, fara suprascriere de comportament — ele raman butoane simple de deschidere, nu
|
||||
comutatoare.
|
||||
|
||||
## 3. Precedent de schimbare a `Picture` la runtime (comutator modifica/salveaza)
|
||||
|
||||
**Exista precedent, si e exact tiparul cerut.** `COMUN\clase\rulaje.vc2`, metoda
|
||||
`frm_rulaje.se_modifica_assign` (assign-method pe proprietatea `se_modifica` a formularului),
|
||||
`rulaje.vc2:4716-4773`:
|
||||
|
||||
```
|
||||
4745: If m.llEditMode && TREC IN MODUL EDITARE
|
||||
4747: With Thisform.but_modifica1
|
||||
4748: .cpicturedown = "save_jos.bmp"
|
||||
4749: .cpictureup = "save_sus.bmp"
|
||||
4750: .Picture = "save_sus.bmp"
|
||||
4751: .ToolTipText = "Salvare"
|
||||
4752: .Enabled = .T.
|
||||
4753: .Refresh()
|
||||
4754: Endwith
|
||||
4755: Else
|
||||
4760: With Thisform.but_modifica1
|
||||
4761: .cpicturedown = "MODIFICA2.BMP"
|
||||
4762: .cpictureup = "MODIFICA1.BMP"
|
||||
4763: .Picture = "MODIFICA1.BMP"
|
||||
4764: .ToolTipText = "Modificare"
|
||||
4765: .Enabled = .T.
|
||||
4766: ENDWITH
|
||||
4769: Endif
|
||||
```
|
||||
|
||||
`but_modifica1` pe `frm_rulaje` e instantiat `ADD OBJECT 'but_modifica1' AS but_modifica WITH ...`
|
||||
(`rulaje.vc2:727-737`, clasa `but_modifica` din `cmd_butoane.vcx`, fara `Picture` propriu pe
|
||||
instanta — pleaca de la creion, cf. clasa). Tiparul confirmat: la comutare se rescriu **impreuna**
|
||||
`.cpicturedown`, `.cpictureup` **si** `.Picture` (nu doar `.Picture` — altfel hover-ul din
|
||||
`buton.MouseEnter`/`MouseLeave`, `_cmd_base.vc2:88-97`, ar reveni la iconita veche la urmatorul
|
||||
mouse-over), plus `.ToolTipText` si `.Refresh()`.
|
||||
|
||||
Nu exista alt precedent care sa comute intre creion si discheta pe acelasi buton; celelalte hit-uri
|
||||
pe `.Picture =` gasite in suita (`baza.vc2`, `otouchscreen.vc2`, `ferestre_seturi_indicatori.vc2`
|
||||
etc.) sunt fie hover MouseEnter/Leave standard (`this.Picture = this.cPictureDown/Up`), fie
|
||||
schimbari de iconita fara legatura cu modificare/salvare (tab-uri, bife, animatii).
|
||||
|
||||
## 4. Imaginea de discheta pe disc si rezolvarea caii
|
||||
|
||||
Fisiere confirmate pe disc (`Get-ChildItem` recursiv in `D:\ROA\ROAFACTURARE`):
|
||||
|
||||
```
|
||||
COMUN\grafice\save_sus.bmp
|
||||
COMUN\grafice\save_jos.bmp
|
||||
COMUN\grafice\modific_sus.bmp
|
||||
COMUN\grafice\modific_jos.bmp
|
||||
COMUN\grafice\modifica1.bmp
|
||||
COMUN\grafice\modifica2.bmp
|
||||
```
|
||||
|
||||
Nu exista niciun `save*.bmp`/`modific*.bmp` sub `D:\ROA\ROAFACTURARE\Grafice` (folderul propriu al
|
||||
proiectului) — toate traiesc in `COMUN\grafice`.
|
||||
|
||||
Rezolvarea caii: clasa foloseste cale relativa la definirea proprietatii (`..\grafice\save_sus.bmp`,
|
||||
rezolvata de VFP fata de `.vcx`-ul clasei la Init). Codul de runtime din `rulaje.vc2` seteaza insa
|
||||
**nume de fisier fara cale** (`"save_sus.bmp"`, `"MODIFICA1.BMP"`), care se rezolva prin
|
||||
`SET PATH` — verificat in `Programe\roafacturare.prg:85-108`: `lcPath` include atat
|
||||
`gcAppPath + 'GRAFICE;'` cat si `gcAppPath + 'COMUN\GRAFICE;'`, `SET PATH TO &lcPath ADDITIVE`. Deci
|
||||
o alocare `.Picture = "save_sus.bmp"` la runtime se rezolva corect, indiferent daca fisierul e in
|
||||
`Grafice\` sau `COMUN\Grafice\`, atata timp cat numele fara cale e folosit (nu resursa inclusa in
|
||||
EXE — nu s-a gasit nicaieri mecanism de resurse compilate pentru aceste bmp-uri).
|
||||
|
||||
## 5. Metoda de biblioteca `do_activeaza`/`do_dezactiveaza` pe clase de buton
|
||||
|
||||
**Neverificat -> verificat, raspuns: nu exista pe clasele de buton.** Cautat in
|
||||
`COMUN\clase\_cmd_base.vc2` (clasele `_cmdbase`, `buton`, `cmd_buton`) — nicio metoda
|
||||
`do_activeaza`/`do_dezactiveaza`. Mecanismul exista doar pe clase de **container/camp**, nu de
|
||||
buton, cf. si planului insusi (`docs\plan_13_unificare_formular_facturare.md`, sectiunea I):
|
||||
`ct_clb_cautare.do_activeaza`/`do_dezactiveaza` (`caut_ora.vc2:780-806`, dar neapelate azi in
|
||||
`ofacturare_comun.vc2`) si `clb_tx_data.dezactiveaza()`/`reactiveaza()` (`lb_tx.vc2:521-538`,
|
||||
singura reteta completa: `ReadOnly` + `TabStop` + butonul de calendar). Pe butoane, controlul se
|
||||
face direct pe `.Enabled`/`.Visible` (asa cum face si `se_modifica_assign` din rulaje.vc2 mai sus).
|
||||
|
||||
## Rezumat pentru implementare
|
||||
|
||||
- Clasa de folosit pentru discheta: `but_salveaza` (`cmd_butoane.vc2:340-352`) — dar tiparul din
|
||||
`rulaje.vc2` **nu instantiaza a doua clasa**; comuta o singura instanta `but_modifica` intre cele
|
||||
doua seturi de imagini prin `.cpicturedown`/`.cpictureup`/`.Picture`. E reteta direct aplicabila
|
||||
cerintei din decizia 9.
|
||||
- Numele de fisier de folosit: `save_sus.bmp` / `save_jos.bmp` (discheta), `modific_sus.bmp` /
|
||||
`modific_jos.bmp` sau `MODIFICA1.BMP` / `MODIFICA2.BMP` (creion — doua perechi echivalente
|
||||
coexista pe disc; `rulaje.vc2` foloseste a doua pereche, clasa foloseste prima).
|
||||
- Fara cale in fata numelui de fisier — se rezolva prin `SET PATH`.
|
||||
515
docs/cercetare/canal_cont_venit_fara_politica.md
Normal file
515
docs/cercetare/canal_cont_venit_fara_politica.md
Normal file
@@ -0,0 +1,515 @@
|
||||
# Exista un canal fara politica de pret catre ACT_TEMP.SCC? (proiect #13)
|
||||
|
||||
Cercetare read-only. Continua `cont_venit_articol_fara_politica.md`, `nota_contabila_fara_politica.md`,
|
||||
`coresp_cont_venchelt.md`, `verif_baza_vie_cont_venit.md`.
|
||||
|
||||
**Mandat schimbat pe parcurs — vezi sectiunea imediat de mai jos.** Sectiunile de la
|
||||
"Verdict, in cinci randuri" incolo sunt cercetarea rundei cu mandatul vechi ("gaseste un canal
|
||||
ocolitor, nu se atinge `pack_facturare`") si raman ca input de proiectare (harta intrarilor spre
|
||||
`SCD`/`SCC`, DDL, ce face `V_CONT`) — nu mai sunt raspunsul principal.
|
||||
|
||||
**HANDOFF (predare de context, nu sarcina noua):** o a treia sarcina a fost primita de la team-lead
|
||||
dupa livrarea proiectarii de mai jos — verificarea celor 37 de linii de comanda cu articol nemembru
|
||||
al politicii lor (proiectul #13, decizia 32). **Nu a fost inceputa** — sesiunea a evaluat contextul
|
||||
consumat pana la acel punct ca fiind aproape de pragul de predare (lectura extinsa de rapoarte mari +
|
||||
export Oracle in aceasta sesiune) si a predat inainte de a deschide o interogare noua, conform Regula
|
||||
zero. Livrabilul cerut pentru acea sarcina e alt fisier,
|
||||
`docs\cercetare\linii_comanda_articol_nemembru.md` — **neinceput, nescris**. Nimic din
|
||||
sesiunea curenta nu e intr-o stare periculoasa: doar `SELECT`-uri Oracle rulate (toate terminate),
|
||||
niciun fisier de cod atins, niciun proces/tranzactie ramas deschis. Urmatorul agent poate porni
|
||||
curat pe sarcina celor 37 de linii, cu contextul din mesajul team-lead-ului (re-confirma cifra,
|
||||
extrage cele 37 de linii cu id_comanda/id_articol/id_pol/data/stare facturare, verifica daca vreuna
|
||||
s-a facturat fara eroare, cauza daca se poate citi din audit, si validarea pe cod a conditiei
|
||||
FACT-024 aplicata pe forma lor).
|
||||
|
||||
---
|
||||
|
||||
## Proiectarea parametrului de cont contabil (mandat nou, Marius 10.08.2026)
|
||||
|
||||
Marius a autorizat explicit modificarea punctuala a `pack_facturare` ("poți să adaugi parametrul
|
||||
contul contabil la `contabilizeaza_articol`") — decizia 27-bis e relaxata. **Aceasta e o proiectare,
|
||||
nu o implementare — niciun fisier de cod nu a fost atins.** Sursa Oracle:
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17217 linii,
|
||||
verificat `wc -l`); toate liniile citate mai jos sunt din **acest fisier**, citite direct in aceasta
|
||||
runda (nu preluate din rapoarte anterioare).
|
||||
|
||||
### Verdict proiectare, in sase randuri
|
||||
|
||||
**Fezabil, cu o precizare importanta fata de formularea lui Marius**: `contabilizeaza_articol`
|
||||
primeste azi un **singur parametru**, `detalii_articol VANZARI_DETALII_TEMP%ROWTYPE` — un rand
|
||||
intreg, nu o lista de scalari. Deci "parametrul de cont" **nu intra ca parametru nou pe
|
||||
`contabilizeaza_articol` insasi** (asta ar fi cosmetic, fara efect, pentru ca cei 3 apelanti interni
|
||||
ii dau deja randul intreg dintr-un `TABLE OF VANZARI_DETALII_TEMP%ROWTYPE`) — intra pe drumul real:
|
||||
**o coloana noua pe `VANZARI_DETALII_TEMP`, populata printr-un parametru nou pe
|
||||
`adauga_articol_factura`** (procedura chemata direct din VFP), pe care `contabilizeaza_articol` o
|
||||
citeste automat prin `detalii_articol.<coloana>`, **fara nicio modificare la `scrie_factura2`,
|
||||
`scrie_factura_avize_retur` sau `scrie_aviz_retur`**. Efectul practic e exact ce a cerut Marius — un
|
||||
cont de venit calculat de VFP ajunge in `ACT_TEMP.SCC` fara politica — doar mecanica de livrare e
|
||||
alta decat "parametru pe `contabilizeaza_articol`" literal. Restul (SCD, CU_TVA, IN_VALUTA,
|
||||
EXPLICATIE, ID_VENCHELT, ID_SECTIE, `descarca_gestiune` o singura data, FACT-024 ocolit doar pe
|
||||
ramura noua) au surse identificate mai jos, cu 2 hardcodari explicite care cer confirmarea lui
|
||||
Marius (`SCD='4111'`, `CU_TVA=1`).
|
||||
|
||||
### 1. Lantul real de apel — de ce parametrul trebuie sa intre pe `adauga_articol_factura`, nu pe `contabilizeaza_articol`
|
||||
|
||||
```
|
||||
ff_...:7173 FUNCTION contabilizeaza_articol(detalii_articol VANZARI_DETALII_TEMP%ROWTYPE) RETURN NUMBER IS
|
||||
```
|
||||
|
||||
Singurul parametru. Apelata de exact **3 locuri**, toate interne pachetului, toate ii dau un element
|
||||
dintr-un array populat integral din tabel:
|
||||
|
||||
```
|
||||
ff_...:6039-6040 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE;
|
||||
tab_detalii tab_detalii_type;
|
||||
ff_...:6063 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP;
|
||||
ff_...:6141 V_INCASAT_CALCUL := V_INCASAT_CALCUL + pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- scrie_factura2
|
||||
ff_...:6858 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- scrie_factura_avize_retur
|
||||
ff_...:7140 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- scrie_aviz_retur
|
||||
```
|
||||
|
||||
`SELECT * BULK COLLECT` (nu o lista explicita de coloane) inseamna: **orice coloana noua adaugata pe
|
||||
`VANZARI_DETALII_TEMP` ajunge automat in `tab_detalii(i)` si deci in `detalii_articol` in
|
||||
`contabilizeaza_articol`, fara sa atingi aceste 3 proceduri.** Asta e drumul cu cea mai mica
|
||||
suprafata de risc posibila — nu exista un drum mai mic care sa duca un cont calculat de VFP pana in
|
||||
`contabilizeaza_articol`.
|
||||
|
||||
Intrarea reala din VFP e `adauga_articol_factura` (nu `contabilizeaza_articol`):
|
||||
|
||||
```
|
||||
ff_...:4989-5015 PROCEDURE adauga_articol_factura(V_ID_TEMP IN NUMBER,
|
||||
V_ID_ARTICOL IN NUMBER,
|
||||
... (23 parametri) ...
|
||||
V_ID_UTIL IN NUMBER,
|
||||
V_TAXCODE IN NUMBER DEFAULT NULL,
|
||||
V_LOT IN VARCHAR2 DEFAULT NULL) IS
|
||||
```
|
||||
|
||||
**Precedent direct pentru "adauga parametru nou la coada, cu `DEFAULT NULL`"**: `V_TAXCODE` si
|
||||
`V_LOT` sunt deja exact asta — doi parametri adaugati ulterior, la finalul listei, cu `DEFAULT NULL`,
|
||||
fara sa oblige la rescrierea apelantilor existenti. Propunerea de mai jos repeta acelasi tipar, nu
|
||||
inventeaza unul nou.
|
||||
|
||||
Apelantul VFP confirmat (singurul gasit in sursa, dublat identic in doua locuri din aceeasi clasa —
|
||||
vezi "Ce nu s-a putut stabili"):
|
||||
|
||||
```
|
||||
COMUN\clase\ofacturare.vc2:14069-14091 (si identic la :18089-18114)
|
||||
lcSql = lcSql + [pack_facturare.adauga_articol_factura(] + Alltrim(Str(poArt.id_temp)) + [,] + ;
|
||||
... (pozitional, 26 de argumente) ... + ;
|
||||
NVL(ALLTRIM(STR(poArt.taxcode)),[NULL]) + ;
|
||||
[,'] + OracleSpecialCharacters(Alltrim(Nvl(poArt.lot,[]))) + ['] + ;
|
||||
[);]
|
||||
```
|
||||
|
||||
Apel **pozitional**, se opreste la `V_LOT` (ultimul parametru existent azi). Oracle permite ca un
|
||||
apel pozitional sa se opreasca inainte de parametri opționali de la coada — deci un parametru nou
|
||||
**dupa** `V_LOT` nu afecteaza acest apel existent (ramane identic, echivalent cu "trimite `NULL`" pe
|
||||
noul parametru).
|
||||
|
||||
### 2. Schema propusa
|
||||
|
||||
**Coloana noua**: `VANZARI_DETALII_TEMP.CONT_VENIT VARCHAR2(4) NULL` (fara `NOT NULL`, fara `CHECK`
|
||||
— acelasi tipar ca `ACT_TEMP.SCC`, vezi DDL mai jos). Recomandat, dar nu strict necesar pentru
|
||||
mecanismul de nota (doar pentru trasabilitate/raportare ulterioara): aceeasi coloana pe
|
||||
`VANZARI_DETALII`, plus adaugarea ei in `INSERT /*+ APPEND */ INTO VANZARI_DETALII (...) SELECT ...
|
||||
FROM VANZARI_DETALII_TEMP` din `scrie_in_vanzari` (citat in `nota_contabila_fara_politica.md`
|
||||
sectiunea 4 ca avand lista explicita de coloane — **linia exacta nu a fost re-verificata in aceasta
|
||||
runda**, vezi "Ce nu s-a putut stabili"). Fara acest pas secundar, nota contabila tot se scrie corect
|
||||
— doar `VANZARI_DETALII` nu ar pastra, dupa fapt, cu ce cont s-a facturat linia.
|
||||
|
||||
**Parametru nou pe `adauga_articol_factura`**, la coada, dupa `V_LOT`:
|
||||
```
|
||||
V_CONT_VENIT IN VARCHAR2 DEFAULT NULL
|
||||
```
|
||||
Plumbing identic cu `V_CONT`/`V_CONT2` (`ff_...:5004,5035-5037,5238,5268` — acelasi tipar de
|
||||
"copiaza daca nu e sentinela/gol, altfel NULL"), adaugat langa `INSERT INTO VANZARI_DETALII_TEMP`
|
||||
(`ff_...:5222-5282`): o coloana in plus in lista, o valoare in plus in `VALUES`.
|
||||
|
||||
**Niciun rand de cod nou in `scrie_factura2` / `scrie_factura_avize_retur` / `scrie_aviz_retur`** —
|
||||
confirmat la sectiunea 1, `SELECT *` + `%ROWTYPE` absoarbe coloana automat.
|
||||
|
||||
### 3. Ramura noua in `contabilizeaza_articol`
|
||||
|
||||
Corpul complet citit `ff_...:7173-7547`. Structura propusa: un `IF detalii_articol.cont_venit IS NOT
|
||||
NULL THEN <ramura noua> ELSE <tot codul de azi, neschimbat> END IF;` care **infasoara inclusiv
|
||||
blocul FACT-024**, plasat imediat dupa declaratiile locale (inainte de `ff_...:7278`). Cand
|
||||
parametrul e `NULL` (toti apelantii de azi, nemodificati), executia cade in `ELSE` si comportamentul
|
||||
e identic bit-cu-bit cu azi.
|
||||
|
||||
**Continutul ramurii noi** (doar pentru "articol simplu" — articolele compuse, `V_COMPUS=1`, raman
|
||||
in afara scopului, ca azi):
|
||||
|
||||
| Camp | Sursa in ramura noua | Argumentatie |
|
||||
|---|---|---|
|
||||
| `SCC` | `detalii_articol.cont_venit` | Chiar parametrul primit — asta e intreg scopul modificarii. |
|
||||
| `SCD` | **hardcodat `'4111'`** pentru cazul "factura" (ramurile aviz raman `'418'`/`'461'`, deja hardcodate azi la `ff_...:7415,7420`, independent de politica) | **Hardcodare explicita, de confirmat cu Marius.** Azi, pentru factura normala, `V_SCD := crs_rand_articol.scd` (`ff_...:7409`) — vine din `NOTE_CONTABILE.SCD`. Pe baza vie (`verif_baza_vie_cont_venit.md` tabel sectiunea 4), **toate cele 7 note de vanzari active au `SCD='4111'`** (contul de clienti, standard pentru vanzare pe credit) — zero exceptii masurate. Decizia 31 (nu generaliza de pe datele din Dev) se aplica la *numere*, nu la *structura contabila* — `4111` = clienti e conventie standard de plan de conturi, nu un artefact al datelor de test, dar tot ramane o presupunere care trebuie confirmata explicit, nu deja "descoperita". |
|
||||
| `ASCD` | `PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD)` | Exact fallback-ul deja folosit necondiționat pentru ramurile de aviz azi (`ff_...:7416-7417,7421-7422`) — functioneaza fara cursor, deja verificat ca nu depinde de politica (`coresp_cont_venchelt.md` sectiunea 7). |
|
||||
| `ASCC` | `PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCC)` | Acelasi tipar ca `V_ASCC` de azi (`ff_...:7426-7428`), care oricum e `NVL(crs_rand_articol.ascc, GetAnaliticByGrupUtilizatori(...))` — fallback-ul e calea normala, nu exceptia. |
|
||||
| `EXPLICATIE` | `detalii_articol.explicatia` (coloana `EXPLICATIA` din `VANZARI_DETALII_TEMP`, populata de `V_EXPLICATIE` — parametru deja existent pe `adauga_articol_factura`, `ff_...:4992,5227,5257`) | **Mai buna decat sursa de azi**, nu doar un fallback — azi `crs_rand_articol.explicatie` vine dintr-un sablon static configurat pe nota; textul introdus de utilizator la adaugarea articolului e deja disponibil pe rand, fara politica. |
|
||||
| `ID_VENCHELT` | `pack_facturare.nid_venchelt` | Acelasi fallback de sesiune pe care cursorul de azi il foloseste oricum ca prioritate (`NVL(pack_facturare.nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT))`, `ff_...:7244-7245`) — poate ramane `NULL` daca variabila de sesiune nu e setata, exact ca azi in acelasi caz. |
|
||||
| `ID_SECTIE` | `pack_facturare.nid_sectie_stoc` | Acelasi tipar (`ff_...:7246`). |
|
||||
| `IN_VALUTA` | `pack_facturare.nin_valuta` | **Nu e o presupunere noua** — e valoarea pe care cursorul de azi o **substituie deja** peste `D.IN_VALUTA` intr-un caz analog (`ff_...:7254-7260`, cand `A.ID_POL = pack_facturare.nid_politica_stoc AND pack_facturare.nin_valuta = 1`). Fiindca fara politica nu exista `D.IN_VALUTA` de citit, se foloseste direct flagul de document — acelasi flag pe care codul existent il trateaza deja ca sursa de incredere. |
|
||||
| `CU_TVA` | **hardcodat `1`** | **Hardcodare explicita, de confirmat cu Marius.** `CU_TVA` (parametrul `V_CU_TVA` al `scrie_nota`, `ff_...:12347`) controleaza daca se scrie **separat** o linie de TVA (`ff_...:12537-12558`, `pack_facturare.scrie_tva(...)`) — nu trebuie confundat cu `V_PRET_ARE_TVA` (mapat pe `detalii_articol.pret_cu_tva`, deja disponibil independent de politica, controleaza doar interpretarea pretului). Pe baza vie, **toate cele 7 note de vanzari active au `CU_TVA=1`** (`verif_baza_vie_cont_venit.md` sectiunea 4) — nicio nota de vanzare reala cu `CU_TVA=0`. Semantic, `1` = comportamentul standard pentru o vanzare taxabila (TVA scrisa separat, necesar pentru raportarea de TVA); `0` n-are niciun exemplu real observat. Daca cota TVA a articolului e 0% (scutit), `scrie_tva` scrie oricum o linie cu suma 0 — inofensiv, nu gresit, dar de confirmat ca e acceptabil. |
|
||||
|
||||
**Apeluri, o singura data fiecare** (nu in bucla — nu exista cursor in aceasta ramura):
|
||||
|
||||
1. `pack_facturare.scrie_nota(...)` — aceiasi parametri ca la `ff_...:7443-7467`, cu valorile de mai
|
||||
sus in locul celor din `crs_rand_articol`; restul (`cantitate`, `pret`, `discount_unitar`,
|
||||
`pret_cu_tva`, `id_valuta`, `curs/multiplicator`, `id_ctr`, `proc_tvav`, `id_jtva_coloana`,
|
||||
`taxcode`) vin din `detalii_articol`, exact ca azi — independente de politica.
|
||||
2. `pack_facturare.descarca_gestiune(...)` — **aceeasi garda ca azi**
|
||||
(`pack_facturare.nscadere_stoc=1 AND detalii_articol.id_gestiune<>-1000 AND
|
||||
detalii_articol.in_stoc=1`, `ff_...:7473-7475`), cu `crs_rand_articol.id_sectie`/`.id_venchelt`
|
||||
inlocuite de `pack_facturare.nid_sectie_stoc`/`.nid_venchelt` direct (fara cursor). **Ruleaza
|
||||
exact o data, prin constructie** — nu exista `WHILE cursor%FOUND LOOP` in aceasta ramura, deci
|
||||
defectul de "set multi-rand" (punctul urmator) nu poate aparea aici, fara sa fi fost nevoie sa se
|
||||
repare nimic pe ramura veche.
|
||||
3. Discount (`ff_...:7501-7518`), aceeasi garda (`discount_unitar<>0 AND ndiscount_evidentiat=1`),
|
||||
cu `V_ASCD` si `V_CU_TVA` calculate mai sus in loc de cele din cursor.
|
||||
4. `RETURN V_INCASAT_CALCUL;` — identic.
|
||||
|
||||
### 4. FACT-024 — ocolit doar pe ramura noua, garda neschimbata pe ramura veche
|
||||
|
||||
Blocul (`ff_...:7278-7302`) ramane **litera cu litera identic** in `ELSE`. Nu se slabeste nimic — o
|
||||
linie fara politica **si** fara `cont_venit` continua sa primeasca FACT-024 exact ca azi. Bypass-ul
|
||||
e strict conditionat de faptul ca VFP a trimis explicit un cont (`cont_venit IS NOT NULL`), nu de
|
||||
absenta politicii in sine.
|
||||
|
||||
### 5. Bug-ul de set multi-rand — nemostenit, netratat
|
||||
|
||||
Documentat deja (`nota_contabila_fara_politica.md`, `verif_baza_vie_cont_venit.md` sectiunea 6.1):
|
||||
`cursor_articol` face `LEFT JOIN NOTE_CONTABILE D ON C.ID_SET = D.ID_SET` fara `ROWNUM`/agregare, iar
|
||||
bucla scrie venitul si descarca gestiunea o data per rand din set. **Ramura noua nu are cursor deloc**
|
||||
— rezolva structural aceeasi clasa de bug, dar **nu e propusa ca reparatie a ramurii vechi**; ramane
|
||||
un defect separat, de tratat separat, cum a cerut explicit team-lead-ul.
|
||||
|
||||
### 6. Suprafata de regresie
|
||||
|
||||
- **`contabilizeaza_articol`**: 3 apelanti, toti interni `pack_facturare` (`scrie_factura2`,
|
||||
`scrie_factura_avize_retur`, `scrie_aviz_retur`) — **zero schimbari** la niciunul (sectiunea 1).
|
||||
- **`adauga_articol_factura`**: **un singur apelant confirmat** in sursa citita,
|
||||
`COMUN\clase\ofacturare.vc2:14069-14091`, duplicat identic la `:18089-18114` in aceeasi clasa
|
||||
(posibil doua metode gemene, nu doua clase — neconfirmat, vezi mai jos). Apel pozitional, se
|
||||
opreste la `V_LOT` — **neafectat** de un parametru nou dupa `V_LOT` cu `DEFAULT NULL`, pe acelasi
|
||||
tipar deja folosit de `V_TAXCODE`/`V_LOT` insele.
|
||||
- **Restul suitei** (ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB): pe baza cercetarilor anterioare
|
||||
(`nota_contabila_fara_politica.md` sectiunea 4, `coresp_cont_venchelt.md` sectiunea 9b), aceste
|
||||
produse folosesc `adauga_articol_factura_deviz`/`_stoc` (proceduri **separate**, semnaturi diferite,
|
||||
**neatinse** de aceasta propunere) sau `pack_acn.salveaza_regdoc` (alt pachet complet). O cautare
|
||||
directa, in aceasta runda, pentru apeluri catre `adauga_articol_factura(` (fara `_deviz`/`_stoc`) in
|
||||
`COMUN`-urile celorlalte produse **nu s-a terminat in timp util** (comanda de fond nu a raspuns) —
|
||||
vezi "Ce nu s-a putut stabili". Pe baza dovezilor deja existente (nimeni altcineva nu are un flux
|
||||
documentat prin `adauga_articol_factura` simplu), riscul asteptat e **zero**, dar nu e demonstrat
|
||||
exhaustiv pentru toata suita in aceasta runda.
|
||||
- **Teste minime recomandate**: (a) factura normala cu politica, parametru `NULL` — verifica ACT
|
||||
identic cu azi (regresie); (b) articol fara politica, `cont_venit` populat — un singur rand `ACT`,
|
||||
`SCC`=valoarea data, `SCD='4111'`, linie TVA scrisa separat; (c) articol negestionabil
|
||||
(`id_gestiune=-1000`) cu `cont_venit` populat — `descarca_gestiune` NU ruleaza; (d) discount pe
|
||||
linie cu `cont_venit` populat; (e) document in valuta (`nin_valuta=1`) cu `cont_venit` populat —
|
||||
`IN_VALUTA=1` pe nota, `SUMA_VAL` completat; (f) linie fara politica **si** fara `cont_venit` —
|
||||
FACT-024 tot apare (regresie negativa, garda nu s-a slabit).
|
||||
|
||||
### 7. Alternativa mai mica — nu exista una reala
|
||||
|
||||
Propunerea de mai sus **e** deja varianta minimala: un singur parametru `VARCHAR2(4) DEFAULT NULL`,
|
||||
zero schimbare de comportament cand e `NULL`, zero atingere a celor 3 apelanti interni. O varianta
|
||||
"doar un flag care opreste FACT-024, fara sa transporte contul" tot ar avea nevoie ca `SCC` sa vina
|
||||
de undeva — nu reduce suprafata, doar muta numele. Nu recomand separarea in doi parametri
|
||||
(flag + cont) — complexitate in plus fara beneficiu; un singur camp nenul e semnalul suficient.
|
||||
|
||||
### Ce nu s-a putut stabili in aceasta runda, si de ce
|
||||
|
||||
- **Daca `ofacturare.vc2:14069-14091` si `:18089-18114` sunt doua metode/clase distincte sau o
|
||||
duplicare literala a aceleiasi metode** — nu am identificat clasele-container ale celor doua
|
||||
blocuri (ar necesita indexul de simboluri `vfp_symbols.ps1`, nefolosit in aceasta runda din lipsa
|
||||
de timp). Nu schimba verdictul (ambele se opresc la `V_LOT`), dar conteaza pentru a sti cate locuri
|
||||
VFP trebuie atinse ca sa se **foloseasca** efectiv noul parametru dupa ce va exista in Oracle.
|
||||
- **Daca alte produse (ROAGEST/ROAAUTO/ROAACNPRO/ROAIMOB) apeleaza `adauga_articol_factura` simplu**
|
||||
undeva neexaminat — comanda de cautare pe `COMUN`-urile celorlalte produse a ramas fara raspuns in
|
||||
timp util in aceasta sesiune; de re-rulat separat inainte de implementare.
|
||||
- **Linia exacta a `INSERT ... INTO VANZARI_DETALII ... SELECT ... FROM VANZARI_DETALII_TEMP`** din
|
||||
`scrie_in_vanzari` — citata doar din raportul anterior (`nota_contabila_fara_politica.md`), nu
|
||||
re-verificata pe fisierul curent in aceasta runda; necesara doar daca se decide sa se persiste si
|
||||
`CONT_VENIT` pe `VANZARI_DETALII` (pasul optional de la sectiunea 2).
|
||||
- **Comportamentul `scrie_tva` cu cota 0%** (linie scutita) cand `CU_TVA` e hardcodat la `1` — nu am
|
||||
citit corpul `scrie_tva` in aceasta runda pentru a confirma ca o suma 0 nu produce vreun efect
|
||||
secundar (ex. impact pe `nproc_tva_max`/`nid_jtva_coloana` la `ff_...:12538-12541`, care se
|
||||
actualizeaza necondiționat de valoarea sumei).
|
||||
- **Confirmarea explicita a lui Marius pe cele doua hardcodari** (`SCD='4111'`, `CU_TVA=1`) — sunt
|
||||
presupuneri argumentate din date reale si din tiparul de cod existent, nu descoperiri; raman
|
||||
decizii de proiectare deschise.
|
||||
|
||||
---
|
||||
|
||||
## Verdict runda anterioara (canal ocolitor, mandat vechi — pastrat ca input de proiectare)
|
||||
|
||||
**DA, exista un canal real, deja cablat si deja folosit in productie pentru categoria de document
|
||||
"facturi emise"** — complet independent de `pack_facturare` si de politica de pret. E cursorul VFP
|
||||
generic `actactan`/`tact` -> `oscrie_in_fisiere.prg` (INSERT generic in `ACT_TEMP` din campurile
|
||||
oricarui cursor VFP) -> `pack_contafin.SCRIE_IN_ACT`/`STERGE_DIN_ACT` (nu `pack_facturare`!). Cel mai
|
||||
relevant: **fluxul de editare a facturii emise, `ofacturare_comun.vc2:3796-3821` (proiectul #6
|
||||
insusi), foloseste exact acest canal** ca sa lase utilizatorul sa editeze `SCD`/`ASCD`/`SCC`/`ASCC`
|
||||
direct pe randul notei prin `frm_modific2024` (`omodificari.vc2`, fisier **nerestrictionat**), apoi
|
||||
rescrie `ACT_TEMP` cu valorile din cursorul VFP editat. **Limitarea majora**: canalul e cablat pe
|
||||
**editarea unei note deja scrise** (sterge + rescrie), nu pe **emiterea** unei facturi noi — emiterea
|
||||
trece azi exclusiv prin `pack_facturare.scrie_factura2 -> contabilizeaza_articol`, care nu are nicio
|
||||
ramura alternativa (confirmat in rundele anterioare, FACT-024). Pistele 1, 2, 4, 5 din cerere sunt
|
||||
**toate NU** — niciuna nu ofera un canal catre `SCC`. DDL-ul `ACT_TEMP` nu blocheaza nimic din asta:
|
||||
`SCD`/`SCC` sunt `VARCHAR2(4) NULL`, fara `CHECK`.
|
||||
|
||||
Sursa principala pentru citatele Oracle: **`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`**
|
||||
(17217 linii — confirmat `wc -l`). Fisierul din `docs\` nu mai exista, cum a semnalat cererea.
|
||||
|
||||
---
|
||||
|
||||
## Pistele cerute, in ordine
|
||||
|
||||
### 1. `V_CONT IN VARCHAR2` (parametru `adauga_articol_factura`) — **NU e canal pentru SCC**
|
||||
|
||||
Confirmat pe cod, nu doar preluat din raportul anterior (a carui concluzie pe acest punct era deja
|
||||
corecta): `V_CONT` e contul de **gestiune/stoc** (clasa 3xx), nu de venit. E scris ca atare in
|
||||
`VANZARI_DETALII_TEMP.CONT` (`adauga_articol_factura`, semnatura la
|
||||
`ff_...:4989-5015` `V_CONT IN VARCHAR2`, folosire la `:5044-5046` `V_CONT2 := V_CONT`, `INSERT` la
|
||||
`:5277`). **Niciodata folosit ca `SCD`/`SCC`** — `contabilizeaza_articol` isi ia `V_SCC` exclusiv din
|
||||
`cursor_articol` (`D.SCC`, lantul `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI
|
||||
-> NOTE_CONTABILE`, `ff_...:7248-7253,7259-7260`), fara nicio ramura care sa citeasca `V_CONT`.
|
||||
`V_CONT` alimenteaza doar `descarca_gestiune` (contul de iesire din gestiune, SCC pe nota de
|
||||
**stoc**, nu de venit), nu nota de venit.
|
||||
|
||||
### 2. Variabile de sesiune `pack_facturare` — **NU exista setter pentru cont**
|
||||
|
||||
Am listat toate variabilele publice de pachet cu prefix `nid_` din specificatia `pack_facturare`
|
||||
(`ff_...:72-211`, citat exhaustiv, cautare `grep -n "^ nid_"`):
|
||||
|
||||
```
|
||||
nid_tipnir, nid_tipbon, nid_tipfactura, nid_tipaviz, nid_act, nid_serie, nid_fdoc, nid_part,
|
||||
nid_part_rez, nid_lucrare, nid_sectie_stoc, nid_gestiune_sursa, nid_responsabil, nid_ordl, nid_set,
|
||||
nid_util, nid_moneda_nationala, nid_fact, nid_factc, nid_partc, nid_jtva_coloana, nid_comanda,
|
||||
nid_valuta, nid_politica_stoc, nid_sucursala, nid_venchelt, nid_vanzare, nid_beneficiar
|
||||
```
|
||||
|
||||
Niciuna tipata pe un cont (`VARCHAR2(4)`/cod de cont) — toate sunt FK-uri numerice
|
||||
(`%TYPE` pe alte tabele: `ID_SECTIE`, `ID_VENCHELT`, `ID_POL`, etc.). Cautare directa
|
||||
`nid_scc`/`nid_scd`/`nid_cont` in tot fisierul: **zero rezultate**. `nid_venchelt` (folosit ca
|
||||
fallback la `ID_VENCHELT` in `cursor_articol`, `ff_...:7244-7245`, deja documentat in
|
||||
`coresp_cont_venchelt.md` sectiunea 7) e o clasificare dimensionala
|
||||
(`NOM_VENIT_CHELTUIELI.ID_VENCHELT%TYPE`), nu un cont — **nu am gasit consumator care sa-l foloseasca
|
||||
drept `SCC`**. Cautare separata `PROCEDURE SET_` in tot pachetul: zero rezultate — nu exista niciun
|
||||
setter public de tip `SET_...` pentru cont. **Concluzie: NU.**
|
||||
|
||||
### 3. `ACT_TEMP` scris direct din VFP — **DA, canal real, dar pe fluxul de editare, nu de emitere**
|
||||
|
||||
Confirmat de `COMUN\docs\oracle_export.md:64-65`: *"Pachetul central de scriere a documentelor —
|
||||
clientul VFP populeaza tabelele temporare `ACT_TEMP`/`RUL_TEMP` (prin `oscrie_in_fisiere`-ul din
|
||||
COMUN), apoi pachetul distribuie"*. Mecanismul e generic si complet independent de `pack_facturare`:
|
||||
|
||||
**Mecanismul** (`COMUN\programe\oscrie_in_fisiere.prg`):
|
||||
- `sql_temp_insert('actactan','ACT_TEMP')` (`oscrie_in_fisiere.prg:127`) face, pentru fiecare rand al
|
||||
oricarui cursor VFP numit `actactan`, un `INSERT INTO ACT_TEMP (<coloanele care exista si in
|
||||
cursor si in tabel>) VALUES (...)` construit dinamic din `user_tab_columns`
|
||||
(`oscrie_in_fisiere.prg:190-280`) — **orice camp `SCD`/`ASCD`/`SCC`/`ASCC` prezent in cursorul VFP
|
||||
ajunge direct in `ACT_TEMP`, indiferent de valoare, fara nicio validare de cont**.
|
||||
- Scrierea efectiva in `ACT` se face prin `pack_contafin.init_scriere_act_rul_local` +
|
||||
`final_scriere_act_rul_local` -> `SCRIE_IN_ACT`/`STERGE_DIN_ACT` (`oscrie_in_fisiere.prg:121-147`)
|
||||
— **`pack_contafin`, nu `pack_facturare`**. Confirmat si in
|
||||
`COMUN\docs\flux-modificare-stergere-nota-jurnal.md:20-28`.
|
||||
|
||||
**Cine populeaza `actactan` cu valori calculate/editate de VFP** — cele doua cazuri gasite, ambele
|
||||
active in productie:
|
||||
|
||||
**(a) Registrul Jurnal — editare/stergere manuala de nota, orice document, generic**
|
||||
(`COMUN\clase\comun.vc2`, clasa `afisjurcom`):
|
||||
- `do_modifica` (`comun.vc2:2222-2562`): incarca nota existenta
|
||||
(`Select * From v_act Into Cursor actactan`, `comun.vc2:2363`), instantiaza
|
||||
`frm_modific2024` (`comun.vc2:2439`, `omodificari.vc2:6375`) — formular in care utilizatorul poate
|
||||
edita direct `scd`/`ascd`/`scc`/`ascc` pe fiecare rand al notei (grid legat pe `tact`, verificare
|
||||
prin `verific_analitic`/`do_verifica`, tipar prezent si in `omodificari.vc2:1714-1788` — comentat
|
||||
azi, dar acelasi tipar activ e in `frm_modific2024`, vezi mai jos (b)). Dupa editare:
|
||||
`oscrie_in_fisiere(2,...)` = sterge nota veche (`comun.vc2:2454`), apoi
|
||||
`oscrie_in_fisiere(0,...)` = scrie nota noua **cu valorile din cursorul editat**
|
||||
(`comun.vc2:2482`), apoi `pack_contafin.finalizeaza_modificare_nota(...)`
|
||||
(`comun.vc2:2484-2486`). **Niciun apel catre `pack_facturare` in acest flux.**
|
||||
- Documentat integral, cu aceleasi citate, in `COMUN\docs\flux-modificare-stergere-nota-jurnal.md`.
|
||||
- E generic pe orice document din `ACT` (nu doar facturi) — daca o factura emisa are deja o nota,
|
||||
poate fi editata de aici.
|
||||
|
||||
**(b) Editarea facturii emise — acelasi mecanism, scop specific "facturi emise" (proiectul #6
|
||||
insusi)**, `COMUN\clase\ofacturare_comun.vc2:3769-3838` (functia `do_editare_factura`, citita
|
||||
integral, **nu modificata**):
|
||||
|
||||
```
|
||||
ofacturare_comun.vc2:3769 If IncarcaCursoareModificareNota(lnCod, pnAn, pnLuna, .F.) && ofacturare_editare.prg
|
||||
...
|
||||
ofacturare_comun.vc2:3787 Select a.*, Iif(Nvl(id_jtva_coloana,0)=0,0,1) As cu_Tva From tact a Into Cursor tact Readwrite
|
||||
...
|
||||
ofacturare_comun.vc2:3796 Omodif = Createobject([frm_modific2024], lnIdSet)
|
||||
ofacturare_comun.vc2:3797 Omodif.Show()
|
||||
ofacturare_comun.vc2:3799 If buton = 1
|
||||
ofacturare_comun.vc2:3800 If Thisform.do_deschide_tranzactie()
|
||||
ofacturare_comun.vc2:3801 Select actactan
|
||||
ofacturare_comun.vc2:3802 lnSucces = OSCRIE_IN_FISIERE(2,.T.,.T.) && sterge nota veche
|
||||
ofacturare_comun.vc2:3803 If lnSucces > 0
|
||||
...
|
||||
ofacturare_comun.vc2:3807 Select tact
|
||||
ofacturare_comun.vc2:3808 Replace id_jtva_coloana With Null, proc_tva With 0 For cu_Tva = 0
|
||||
ofacturare_comun.vc2:3809 Select * From tact Into Cursor actactan Readwrite && re-materializeaza tact (inclusiv orice SCD/SCC editat) in actactan
|
||||
ofacturare_comun.vc2:3810 Replace All id_util With gnIdUtil, sters With 0
|
||||
...
|
||||
ofacturare_comun.vc2:3821 lnSucces = OSCRIE_IN_FISIERE(0,.T.,.T.) && scrie nota noua, direct din actactan, in ACT_TEMP
|
||||
ofacturare_comun.vc2:3823 If lnSucces > 0
|
||||
ofacturare_comun.vc2:3824 lcSql = [begin pack_contafin.finalizeaza_modificare_nota(...); end;]
|
||||
ofacturare_comun.vc2:3828 If lnSucces > 0 And "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) And Used('tvanz')...
|
||||
ofacturare_comun.vc2:3829 lnSucces = Iif(ScrieArticoleFacturaEditate(tvanz.id_vanzare), 1, -1) && scrie si VANZARI_DETALII
|
||||
```
|
||||
|
||||
`IncarcaCursoareModificareNota` (`COMUN\programe\ofacturare_editare.prg:32-71`) incarca nota
|
||||
existenta a facturii (`vact_tot -> v_act -> actactan -> tact`, toate **READWRITE**) inainte de a
|
||||
arata `frm_modific2024`. Linia `:3809` **rescrie `actactan` direct din `tact`** — orice valoare
|
||||
aflata pe `tact.scd`/`tact.scc` in acel moment (editata manual prin `frm_modific2024`, sau setata
|
||||
programatic de orice cod VFP inainte de linia 3809) devine noua nota, scrisa in `ACT_TEMP` prin
|
||||
`OSCRIE_IN_FISIERE(0,...)` la linia 3821 — **fara niciun apel `pack_facturare`, fara politica de
|
||||
pret**. `pack_contafin.finalizeaza_modificare_nota` (linia 3824) doar re-leaga `cod`-ul nou de
|
||||
`VANZARI`; `ScrieArticoleFacturaEditate` (linia 3829) scrie separat `VANZARI_DETALII`.
|
||||
|
||||
**Aceasta e dovada cea mai puternica posibila pentru intrebarea pusa**: canalul nu e teoretic — e
|
||||
**exact mecanismul pe care proiectul #6 il foloseste azi** ca sa permita editarea contului unei
|
||||
facturi deja emise, complet in afara `pack_facturare`.
|
||||
|
||||
**Limitarea reala**: mecanismul opereaza pe **o nota deja existenta** (sterge randul vechi din `ACT`,
|
||||
scrie unul nou cu acelasi `id_fact`) — presupune ca factura a fost deja scrisa o data (prin
|
||||
`pack_facturare`, cu politica). Nu e (azi) un canal pentru **emiterea** initiala a unei facturi noi
|
||||
cu un articol fara politica — `scrie_factura2 -> contabilizeaza_articol` (calea de emitere) nu are
|
||||
nicio ramura care sa ocoleasca politica si nu apeleaza acest mecanism.
|
||||
|
||||
**Alte doua confirmari ale aceluiasi tipar, tot pentru categoria "facturi"**:
|
||||
- `COMUN\ferestre\frm_initializare_facturi_balanta.sc2:458` — `CREATE CURSOR actactan (id_set I, an
|
||||
N(4), luna N(2), Dataact D, datascad D, dataireg D, serie_act C(20), nract N(20), proc_tva N(5,2),
|
||||
id_jtva_coloana I NULL, scd C(4), ascd C(4), scc C(4), ascc C(4), ...)` — cursor creat de la zero
|
||||
in VFP, cu `scd`/`scc` campuri simple, editabile liber, fara nicio derivare Oracle. Foloseste tot
|
||||
`frm_modific2024` (`:1806`) pentru editare/validare inainte de scriere.
|
||||
- `COMUN\ferestre\frm_import_note_facturi_clienti.sc2:667,815,898,911` — acelasi tipar
|
||||
(`actactan`/`tact` editabil + `frm_modific2024`), pentru import manual de note pe facturi de
|
||||
clienti.
|
||||
|
||||
Ambele sunt scenarii de **initializare/import** (sold de deschidere, import de note), nu de
|
||||
facturare curenta — dar confirma ca `ACT_TEMP` pentru **categoria "facturi"** se scrie deja, in mod
|
||||
curent, direct din cursoare VFP construite de la zero, fara `pack_facturare`.
|
||||
|
||||
### 4. `ID_VENCHELT` / `CRM_NOTE_VANZARI` — **NU e canal alternativ catre SCC**
|
||||
|
||||
Deja stabilit exhaustiv in `coresp_cont_venchelt.md` sectiunea 7: `ID_VENCHELT` e o dimensiune
|
||||
(`NOM_VENIT_CHELTUIELI.ID_VENCHELT`), citita cu fallback pe `nid_venchelt`
|
||||
(`ff_...:7244-7245`), dar **nu participa la calculul `SCC`** — `V_SCC` vine exclusiv din
|
||||
`cursor_articol.D.SCC`. Nu exista camp de tip cont pe `VANZARI`/`VANZARI_DETALII` — reconfirmat aici
|
||||
prin cautare `grep -inE "SCC|CONT_VENIT"` in blocurile `CREATE`/`ALTER TABLE VANZARI` din
|
||||
`ff_...` (fara rezultate suplimentare fata de ce era deja stabilit).
|
||||
|
||||
### 5. `GetAnaliticByGrupUtilizatori` — **doar analitic, nu cont sintetic**
|
||||
|
||||
Confirmat deja in `coresp_cont_venchelt.md` sectiunea 7: functia da fallback pentru `ASCD`/`ASCC`
|
||||
(analiticul), nu pentru `SCD`/`SCC` (contul sintetic). Nu exista o functie analoaga pentru cont —
|
||||
cautare `GetSinteticBy`/`GetContBy` in tot `ff_...`: zero rezultate.
|
||||
|
||||
---
|
||||
|
||||
## Lista exhaustiva a intrarilor catre `SCD`/`SCC` in `ACT_TEMP`, pe/langa fluxul de facturare
|
||||
|
||||
Sursa: `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` + fisierele VFP citate.
|
||||
|
||||
1. **`pack_facturare.contabilizeaza_articol -> scrie_nota`** (`ff_...:7218-7556` / `12338-12570`,
|
||||
`INSERT INTO ACT_TEMP` la `:12458-12544`) — calea principala de **emitere**. `SCD`/`SCC` vin din
|
||||
`cursor_articol` (`D.SCD`, `D.SCC`, lantul politicii). Blocheaza cu FACT-024
|
||||
(`:7275-7311`) daca articolul nu e in politica — fara fallback (deja stabilit in
|
||||
`nota_contabila_fara_politica.md`).
|
||||
2. **Ramurile speciale din `scrie_factura2`** (transfer subunitati `ntip IN (23,25,30,41)`, custodie
|
||||
`ntip IN (42,47)`, rata `id_rata<>0`) — tot `contabilizeaza_articol`, acelasi punct 1.
|
||||
3. **`scrie_factura_avize_retur`** (`ff_...:6273-6666`, apel la `:6867`) si **`scrie_aviz_retur`**
|
||||
(`ff_...:7094-7180`, apel la `:7149`) — tot `contabilizeaza_articol`.
|
||||
4. **`descarca_gestiune`** (`ff_...:7476-7498`, apelata din interiorul buclei `cursor_articol`) —
|
||||
scrie o a doua nota, de **iesire din gestiune** (`SCD`/`SCC` = conturi de stoc, din `V_CONT`/
|
||||
`poArticol.Cont`, nu din politica) — parte a fluxului de facturare, dar nu e nota de venit.
|
||||
5. **Discount pe linie** (`ff_...:7501-7518`) — scrie o nota separata (vazuta pe date live ca
|
||||
`id_nota=2`, `SCD=667`/`SCC=4111`, `verif_baza_vie_cont_venit.md` sectiunea 4) — tot in interiorul
|
||||
`cursor_articol`, deci tot dependenta de politica pentru a ajunge acolo.
|
||||
6. **`oscrie_vanzare_din_stoc`** (`COMUN\programe\ofacturare_stoc.prg:104-450`) — flux ROAGEST
|
||||
"vanzare din stoc". Apeleaza `pack_facturare.adauga_articol_factura_stoc` per articol
|
||||
(`:357-378`) apoi `oscrie_in_fisiere(0,.F.,.T.)` (`:392`). **Neverificat complet**: codul citit nu
|
||||
arata explicit unde/cum se populeaza `actactan` cu `SCD`/`SCC` inainte de linia 392 in acest flux
|
||||
particular (cursorul `ACTACTAN` e doar *citit* la `:253-289` pentru alte campuri, nu am gasit
|
||||
punctul de scriere in fisierul citit) — posibil populat de o procedura anterioara neexaminata.
|
||||
Marcat ca zona neinchisa, vezi "Ce nu s-a putut stabili".
|
||||
7. **Registrul Jurnal — editare/stergere manuala** (`COMUN\clase\comun.vc2:2222-2724`, clasa
|
||||
`afisjurcom`) — canalul confirmat la pista 3(a). Genereic, orice document din `ACT`.
|
||||
8. **Editare factura emisa** (`COMUN\clase\ofacturare_comun.vc2:3769-3838`, proiectul #6) — canalul
|
||||
confirmat la pista 3(b), specific "facturi emise".
|
||||
9. **`frm_initializare_facturi_balanta.sc2`** si **`frm_import_note_facturi_clienti.sc2`** — canalul
|
||||
confirmat la pista 3, scop initializare/import pe categoria "facturi".
|
||||
10. **eFactura import** (`COMUN\clase\anaf_efactura.vc2:12241,13087-13102,13244-13325`) — acelasi
|
||||
tipar generic (`actactan`/`tact` + `frm_modific2024` + `oscrie_in_fisiere`), dar pentru facturi
|
||||
de **achizitie** importate din XML ANAF, nu facturi emise — mentionat pentru completitudine, nu
|
||||
aplicabil direct la #13.
|
||||
11. **`ointroduceri.vc2`/`ointroduceri.prg`** (NIR, BON, RETUR, intrare din gestiune valorica) si
|
||||
**`oschimbare_pret.prg`**, **`oproceduri_rulaje.prg`**, **`reglari_denominare2005.prg`** —
|
||||
acelasi canal generic `actactan`/`oscrie_in_fisiere`, dar pentru alte categorii de document
|
||||
(gestiune, nu vanzare/factura). Mentionate pentru completitudinea listei de consumatori ai
|
||||
canalului, nu aplicabile la #13.
|
||||
|
||||
**Niciuna dintre intrarile 1-6 (fluxul de emitere efectiv) nu are o ramura care sa ocoleasca
|
||||
politica.** Intrarile 7-11 folosesc toate acelasi canal generic (punctul 3), dar niciuna nu e
|
||||
cablata pe fluxul de **emitere** — toate opereaza pe note deja existente sau pe alte categorii de
|
||||
document.
|
||||
|
||||
---
|
||||
|
||||
## DDL `ACT_TEMP` (interogat 10.08.2026, schema `MARIUSM_AUTO`, `ROA_CENTRAL`)
|
||||
|
||||
| Coloana | Tip | Null |
|
||||
|---|---|---|
|
||||
| SCD | VARCHAR2(4) | **Y** |
|
||||
| ASCD | VARCHAR2(4) | Y |
|
||||
| SCC | VARCHAR2(4) | **Y** |
|
||||
| ASCC | VARCHAR2(4) | Y |
|
||||
| COD, NRACT, SUMA, PERECHED, PERECHEC, SUMA_VAL, CURS, NEIMPOZAB, NNIR, ID_UTIL, ID_UTILS, ID_RESPONSABIL, ID_VENCHELT, ID_SECTIE, ID_SET, ID_FACT, ID_PARTD, ID_PARTC, ID_FDOC, ID_LUCRARE, ID_GESTIN, ID_GESTOUT, ID_VALUTA, PROC_TVA, STERS, ID_FACTD, ID_FACTC, VALIDAT, TVA_INCASARE | numeric | N (29 coloane NOT NULL, restul optionale) |
|
||||
|
||||
Restul coloanelor (52 in total) sunt fie `NUMBER` (majoritatea `NOT NULL`), fie `DATE`/`VARCHAR2`
|
||||
opționale (`DATAIREG`, `EXPLICATIA`, `DATASCAD`, `EXPLICATIA4/5`, `ID_SUCURSALA`, `ID_ACT`, `ID_CTR`,
|
||||
`ID_JTVA_COLOANA`, `SERIE_ACT`, `ID_UTILV`, `DATAORAV`, `TAXCODE`, `PAYMENTCODE`).
|
||||
|
||||
Constrangeri: **29 `CHECK` constraints** (`SYS_C0014966`...`SYS_C0014994`) — corespund exact
|
||||
coloanelor `NOT NULL` de mai sus (Oracle genereaza `CHECK (coloana IS NOT NULL)` pentru `NOT NULL`
|
||||
declarat fara nume). **Niciun constraint pe `SCD`/`SCC`** (nici `NOT NULL`, nici `CHECK` de format,
|
||||
nici FK catre un plan de conturi). **Nicio cheie primara/unica** — normal pentru un tabel `_TEMP` de
|
||||
staging. Concluzie: schema **nu blocheaza in niciun fel** o valoare `SCC` calculata de VFP, oricare
|
||||
ar fi ea, inclusiv `NULL`.
|
||||
|
||||
---
|
||||
|
||||
## Ce nu s-a putut stabilit si de ce
|
||||
|
||||
- **Punctul exact unde `ACTACTAN` primeste `SCD`/`SCC` in fluxul `oscrie_vanzare_din_stoc`**
|
||||
(`ofacturare_stoc.prg`, intrarea 6 de mai sus) — codul citit (`:104-450`) nu arata sursa; ar
|
||||
necesita urmarirea completa a apelantului (`initializeaza_vanzare_din_stoc`) si a modulului care
|
||||
populeaza `ACTACTAN` inainte de acest punct (posibil `pack_facturare.adauga_articol_factura_stoc`
|
||||
intoarce un ref cursor legat automat de `goExecutor` pe numele `actactan`, dar nu am gasit apelul
|
||||
explicit). Nu schimba verdictul (oricum ar fi tot `pack_facturare`, deci tot politica), dar ramane
|
||||
o zona neinchisa complet.
|
||||
- **Daca discountul pe linie (`SCD=667`/`SCC=4111`) are vreo cale alternativa in afara
|
||||
`cursor_articol`** — nu verificat aici, presupus dependent de politica la fel ca restul buclei.
|
||||
- **Comportamentul practic al `frm_modific2024` cu privire la validarea contului tastat manual**
|
||||
(daca exista vreo verificare `verific_cont`/plan de conturi la salvare, sau accepta orice text de
|
||||
4 caractere) — nu urmarit pana la capat in `omodificari.vc2`; relevant doar daca se propune
|
||||
reutilizarea canalului 3(b)/3(a) pentru scriere programatica (fara interactiune UI), caz in care ar
|
||||
trebui verificat daca `frm_modific2024` insusi impune vreo validare care ar bloca o scriere
|
||||
automata.
|
||||
- **Daca fluxul de emitere (`scrie_factura2`) ar putea, teoretic, sa scrie intai o factura "goala" de
|
||||
nota (sau cu o nota tehnica minimala) si apoi sa foloseasca imediat, in aceeasi tranzactie logica,
|
||||
mecanismul de la pista 3(b) pentru a corecta `SCC`-ul** — nu explorat aici (ar fi o propunere de
|
||||
design, in afara scopului acestei cercetari read-only); mentionat doar ca directie posibila care
|
||||
reiese direct din ce s-a gasit.
|
||||
- Nu am cautat/citit `pack_contafin.SCRIE_IN_ACT`/`STERGE_DIN_ACT` in `PACK_CONTAFIN.pck` pentru a
|
||||
confirma ca acestea, la randul lor, nu re-deriva `SCC` din altceva — presupunerea (bazata pe
|
||||
`oracle_export.md` si pe `flux-modificare-stergere-nota-jurnal.md`, care descriu deja aceste
|
||||
proceduri drept "distribuie ce a scris clientul", nu "recalculeaza") e ca fac un simplu
|
||||
`INSERT INTO ACT SELECT * FROM ACT_TEMP`-tip transfer, nu o derivare noua — dar nu am deschis
|
||||
personal codul lor in aceasta runda.
|
||||
240
docs/cercetare/cont_venit_corespondente.md
Normal file
240
docs/cercetare/cont_venit_corespondente.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# Corespondenta cont de stoc (3xx) -> cont de venit (7xx): ce exista in cod
|
||||
|
||||
**Context**: continuare a `cont_venit_articol_fara_politica.md`, care stabilise ca `VANZARI_DETALII.CONT`
|
||||
e un cont de **gestiune/stoc** (371, 301-303, 357), fara sa gaseasca un cont de venit pe linia de
|
||||
factura in `pack_facturare`, si lasase neverificat unde se genereaza efectiv contul de venit.
|
||||
Cercetarea de fata raspunde la cele 4 intrebari, cu corectii fata de raportul anterior.
|
||||
|
||||
## Raspuns scurt
|
||||
|
||||
1. **Nu exista un tabel de corespondenta "3xx -> 7xx"** (nume de forma `CORESPONDENTA*`,
|
||||
`NOM_CONTURI*`, `CONT_VENIT*`, `ARTICOLE_CONTABILE*`) nicaieri in `SCRIPTURI_CLAR`. In schimb
|
||||
exista un **mecanism echivalent functional, dar configurat manual, nu derivat automat din contul
|
||||
de stoc**: tabelul `NOTE_CONTABILE` (coloane `SCD`/`ASCD` = cont+analitic debitor,
|
||||
`SCC`/`ASCC` = cont+analitic creditor), legat de politica de pret a articolului prin
|
||||
`CRM_POLITICI_PRETURI.ID_NOTA -> CRM_NOTE_VANZARI.ID_SET -> NOTE_CONTABILE`. Un contabil
|
||||
configureaza acest tabel dintr-un ecran dedicat (`frm_config_note_contabile[2007]`), nu se
|
||||
calculeaza din `NOM_ARTICOLE.CONT`/`STOC.CONT`.
|
||||
2. **`NOM_ARTICOLE.CONT` NU e restrans la conturi de stoc (clasa 3).** Validarea la editare
|
||||
(`verific_cont`) verifica doar ca respectivul cod exista in planul de conturi al anului curent
|
||||
(`vplcont_sintetic`), fara filtru pe clasa. UI-ul trimite explicit catre "Planul de conturi"
|
||||
(buton de help general), nu catre un subset de conturi de stoc. Asta confirma afirmatia lui
|
||||
Marius: campul accepta orice cont, inclusiv 6xx/7xx.
|
||||
3. **Nota contabila a vanzarii SE genereaza in `pack_facturare`** — raportul anterior a cautat
|
||||
literalii `707`/`704`/`706`/`708` (care nu apar hardcodati nicaieri, corect) si a conchis gresit
|
||||
ca lipseste mecanismul. El exista, dar e **indirect**: `pack_facturare.contabilizeaza_articol`
|
||||
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:7182-7556`) ia `SCD`/`SCC` din `NOTE_CONTABILE` (via
|
||||
politica de pret a articolului) si scrie nota cu `pack_facturare.scrie_nota(...)`. `SCC` (contul
|
||||
creditor) e contul de venit efectiv al liniei — nu vine din `VANZARI_DETALII.CONT` (care ramane
|
||||
contul de gestiune, folosit separat, doar pentru descarcarea de gestiune, in
|
||||
`descarca_gestiune`).
|
||||
4. **`ID_VENCHELT`/`NOM_VENIT_CHELTUIELI` nu are coloana de cont** (nicio `ALTER TABLE
|
||||
NOM_VENIT_CHELTUIELI ADD ... CONT` in `SCRIPTURI_CLAR`). E o dimensiune analitica separata
|
||||
(clasificare venituri/cheltuieli, posibil pentru raportare/buget), transmisa ca parametru propriu
|
||||
catre `scrie_nota`, in paralel cu `SCD`/`SCC` — nu e sursa contului.
|
||||
|
||||
## 1. Nu exista tabel de corespondenta 3xx->7xx; exista `NOTE_CONTABILE` + politica de pret
|
||||
|
||||
### Cautare tabel dedicat — negativa
|
||||
Cautari in `D:\ROA\DATABASE\SCRIPTURI_CLAR` (`CREATE TABLE`/`ALTER TABLE`, case-insensitive) pentru
|
||||
`CORESPONDENT*`, `NOM_CONTURI*`, `CONT_VENIT*`, `PLAN_CONTURI*`, `ARTICOLE_CONTABILE*`: niciun
|
||||
rezultat relevant — singurele hituri pe `CORESPONDENT` sunt cuvantul romanesc generic ("banca
|
||||
corespondenta" etc.) in sute de fisiere fara legatura, iar `CONT_VENIT` nu apare deloc ca nume de
|
||||
tabel/coloana.
|
||||
|
||||
### Mecanismul real: lant de 4 tabele, configurat pe politica de pret
|
||||
`pack_facturare.contabilizeaza_articol` (`ff_...:7227-7280`) foloseste acest cursor pentru a afla
|
||||
contul debitor/creditor al liniei de vanzare:
|
||||
|
||||
```sql
|
||||
CURSOR cursor_articol IS
|
||||
SELECT A.ID_POL_ART, A.ID_POL, A.ID_ARTICOL, A.PRET, ...
|
||||
NVL(pack_facturare.nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT)) AS ID_VENCHELT,
|
||||
NVL(pack_facturare.nid_sectie_stoc, C.ID_SECTIE) as ID_SECTIE,
|
||||
C.ID_SET, D.EXPLICATIE, D.SCD, D.ASCD, D.SCC, D.ASCC, D.CU_TVA, ...
|
||||
FROM CRM_POLITICI_PRET_ART A
|
||||
LEFT JOIN CRM_POLITICI_PRETURI B ON A.ID_POL = B.ID_POL
|
||||
LEFT JOIN CRM_NOTE_VANZARI C ON B.ID_NOTA = C.ID_NOTA
|
||||
LEFT JOIN NOTE_CONTABILE D ON C.ID_SET = D.ID_SET
|
||||
WHERE A.ID_POL = detalii_articol.id_pol AND A.ID_ARTICOL = detalii_articol.id_articol;
|
||||
```
|
||||
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:7227-7280`)
|
||||
|
||||
Lantul e: **articol + politica de pret** (`CRM_POLITICI_PRET_ART`, deja documentat in raportul
|
||||
anterior ca avand `PRET`/`PROC_TVAV`/`ID_VALUTA`, fara `CONT`) `->` **antetul politicii de pret**
|
||||
(`CRM_POLITICI_PRETURI.ID_POL`, care are `ID_NOTA`) `->` **nota de vanzare CRM**
|
||||
(`CRM_NOTE_VANZARI.ID_NOTA`, care are `ID_SET`) `->` **sablonul de nota contabila**
|
||||
(`NOTE_CONTABILE.ID_SET`, care are `SCD`/`ASCD`/`SCC`/`ASCC`/`EXPLICATIE`/`CU_TVA`/`IN_VALUTA`).
|
||||
|
||||
Apoi, la scriere (`ff_...:7405-7437`):
|
||||
```sql
|
||||
CASE
|
||||
WHEN pack_facturare.ntip <= 20 or pack_facturare.ntip IN (...) THEN
|
||||
-- factura, factura roahotel
|
||||
V_SCD := crs_rand_articol.scd;
|
||||
V_ASCD := NVL(crs_rand_articol.ascd, PACK_FACTURARE.GetAnaliticByGrupUtilizatori(...));
|
||||
WHEN pack_facturare.ntip in (28, 29) THEN
|
||||
-- aviz catre clienti debitori
|
||||
V_SCD := '461'; ...
|
||||
ELSE
|
||||
-- aviz
|
||||
V_SCD := '418'; ...
|
||||
END CASE;
|
||||
|
||||
V_SCC := crs_rand_articol.scc;
|
||||
V_ASCC := NVL(crs_rand_articol.ascc, PACK_FACTURARE.GetAnaliticByGrupUtilizatori(...));
|
||||
```
|
||||
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:7407-7437`)
|
||||
|
||||
Pentru o factura normala (`ntip <= 20`), `V_SCD` e contul debitor din nota (tipic un cont de
|
||||
creante, 411/etc.), iar `V_SCC` e contul creditor din nota — **acesta e efectiv contul de venit**
|
||||
al liniei (707/704/706/708, in functie de cum a configurat contabilul nota respectiva), preluat
|
||||
**direct din `NOTE_CONTABILE.SCC`**, fara nicio derivare din contul de stoc al articolului. Perechea
|
||||
`V_SCD`/`V_SCC` (plus `ID_VENCHELT`, `ID_SECTIE`, `EXPLICATIE`, cota TVA) e trimisa la
|
||||
`pack_facturare.scrie_nota(...)` (`ff_...:7452-7476`), care scrie randul in nota contabila
|
||||
efectiva a vanzarii.
|
||||
|
||||
**Concluzie fata de ipoteza lui Marius**: mecanismul exista si rezolva exact problema — "de unde
|
||||
stiu contul de venit al unei linii de vanzare, indiferent daca articolul e gestionabil sau nu" —
|
||||
dar **nu e o corespondenta automata cont-stoc -> cont-venit**. E o **configurare manuala per
|
||||
politica de pret**: fiecare politica de pret (`CRM_POLITICI_PRETURI`) e legata la o "nota de
|
||||
vanzare" CRM (`CRM_NOTE_VANZARI`), care la randul ei e legata la un sablon de nota contabila
|
||||
(`NOTE_CONTABILE`) cu perechea SCD/SCC fixata de un contabil. Politica de pret aplicata articolului
|
||||
decide, indirect, contul de venit — nu clasa/valoarea contului de gestiune al articolului.
|
||||
|
||||
### Ecranul de configurare (unde se seteaza SCD/SCC)
|
||||
`D:\ROA\ROAFACTURARE\COMUN\ferestre\frm_config_note_contabile.sc2` (+ varianta
|
||||
`frm_config_note_contabile2007.sc2` pentru anii >= 2007, selectata in
|
||||
`COMUN\programe\omeniu_initializari.prg:257-282`) e formularul din meniu "Configurare note
|
||||
contabile". Clasa e `onote_contabile.vc2`, cu un grid `gridInregistrari` avand coloanele
|
||||
`cScd`/`cAscd`/`cScc`/`cAscc` (`onote_contabile.vc2:2401-2435`) legate direct de
|
||||
`cnote_contab.scd`/`.scc`/`.ascd`/`.ascc`, si salvare prin INSERT/UPDATE direct pe
|
||||
`note_contabile(explicatie,scd,ascd,scc,ascc,ordine,cu_tva,ptva,id_set,in_valuta,...)`
|
||||
(`onote_contabile.vc2:315-322`, `:486-493`). Deci **contabilul e cel care scrie manual perechea
|
||||
SCD/SCC** (inclusiv contul de venit `SCC`) pentru fiecare `id_set`/nota, nu exista automatism care
|
||||
sa deriveze `SCC` din contul de gestiune al articolului.
|
||||
|
||||
## 2. `NOM_ARTICOLE.CONT` — nu e restrans la clasa 3
|
||||
|
||||
- **Camp**: `Clb_tx_cont.Text_simplu1.ControlSource = "porec.cont"`, `MaxLength = 4`, eticheta
|
||||
`"Cont*"` (obligatoriu), buton de help cu tooltip `"Help - Planul de conturi (CTRL+ H)"` —
|
||||
`COMUN\clase\onom_articole.vc2:1058-1087`. Trimiterea catre "Planul de conturi" (nu catre un
|
||||
subset "conturi de stoc") sugereaza ca formularul asteapta orice cont valid, nu doar clasa 3.
|
||||
- **Validare la Valid() al campului** (in alt formular generic de conturi, `onomenclatoare.vc2`, nu
|
||||
in cel de articole, dar foloseste aceeasi functie globala):
|
||||
```
|
||||
PROCEDURE clb_cont.Text_simplu1.Valid
|
||||
IF !EMPTY(this.Value)
|
||||
lnVerificat = verific_cont(thisform.orec.cont)
|
||||
IF lnVerificat < 0
|
||||
RETURN 0
|
||||
ENDIF
|
||||
ENDIF
|
||||
ENDPROC
|
||||
```
|
||||
(`COMUN\clase\onomenclatoare.vc2:3251-3258`)
|
||||
- **`verific_cont`** (`COMUN\programe\oproceduri_comune.prg:2389-2415`):
|
||||
```
|
||||
Procedure verific_cont
|
||||
Parameters tcCont, tlNoMessage
|
||||
lcSel = [SELECT cont from ] + GCS + [.vplcont_sintetic WHERE TRIM(cont) = ?pcCont and an = ?gnAn ]
|
||||
...
|
||||
If Reccount('crs_verific') = 0
|
||||
lnSucces = -1
|
||||
Endif
|
||||
If lnSucces < 0 And !tlNoMessage
|
||||
amessagebox('Acest cont nu este definit in planul de conturi!', 0 + 48, 'Atentie')
|
||||
Endif
|
||||
Endproc
|
||||
```
|
||||
Singura conditie e ca `cont` sa existe in `vplcont_sintetic` (planul de conturi sintetic al
|
||||
anului curent, `gnAn`) — **fara `LIKE '3%'`, fara `SUBSTR(cont,1,1) = '3'`, fara nicio alta
|
||||
restrictie de clasa**. O varianta identica exista si in
|
||||
`COMUN\programe\ooperatii_comune.prg:1307` (nu am comparat corp cu corp, dar semnatura e aceeasi).
|
||||
- Nu exista `CHECK CONSTRAINT` pe `NOM_ARTICOLE.CONT` in `SCRIPTURI_CLAR` (cautare
|
||||
`CHECK.*CONT`/`constraint.*CONT.*check` — zero rezultate relevante), deci nici la nivel de
|
||||
baza de date nu exista o restrictie de clasa.
|
||||
- Nu am gasit nicaieri in `COMUN` sau `ROAFACTURARE` o ramificare pe `Left(cont,1)`/
|
||||
`SUBSTR(cont,1,1)` aplicata specific campului `NOM_ARTICOLE.CONT` (cautare in
|
||||
`D:\ROA\ROAFACTURARE` si `D:\ROA\ROAFACTURARE\COMUN`) — singurul loc cu `SUBSTR(cont,1,1)` gasit e
|
||||
in `onomenclatoare.vc2:3647`, un filtru de grid pe planul de conturi general (tab-uri "1", "2",
|
||||
"3"... pentru navigare in plan), fara legatura cu articolele.
|
||||
|
||||
**Concluzie**: campul e validat generic (orice cont din planul de conturi al anului), fara
|
||||
restrictie de clasa la nivel de UI sau DB. Asta confirma ce a spus Marius: un articol negestionabil
|
||||
poate avea legitim un cont 6xx/7xx in `NOM_ARTICOLE.CONT`.
|
||||
|
||||
## 3. Unde se genereaza efectiv nota contabila a vanzarii
|
||||
|
||||
Raportul anterior cauta gresit literalii `707`/`704`/`706`/`708` (niciunul nu apare hardcodat — corect)
|
||||
si a conchis ca nu exista mecanism in `pack_facturare`. De fapt **exista, dar e in `pack_facturare`,
|
||||
nu intr-un pachet separat de contabilitate**:
|
||||
|
||||
- **Functia**: `pack_facturare.contabilizeaza_articol(detalii_articol VANZARI_DETALII_TEMP%ROWTYPE)`
|
||||
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:7182-7556`), apelata pe fiecare rand din
|
||||
`VANZARI_DETALII_TEMP` — vezi apelul `V_INCASAT_CALCUL := V_INCASAT_CALCUL +
|
||||
pack_facturare.contabilizeaza_articol(tab_detalii(i));` in `scrie_aviz_retur`
|
||||
(`ff_...:7148-7150`); acelasi tipar se repeta si in fluxul principal de facturare (nu doar in
|
||||
aviz-retur — semnatura si structura functiei arata ca acopera si `ntip <= 20`, adica factura
|
||||
propriu-zisa, prin `CASE ... WHEN pack_facturare.ntip <= 20 ...` la `ff_...:7409-7421`).
|
||||
- **Sursa `SCD`/`SCC`**: vezi sectiunea 1 — cursorul `cursor_articol` (`ff_...:7227-7280`), pe baza
|
||||
politicii de pret a articolului (`detalii_articol.id_pol`), nu pe `VANZARI_DETALII.CONT`.
|
||||
- **`VANZARI_DETALII.CONT` ramane folosit separat, doar pentru gestiune**: in aceeasi functie,
|
||||
`descarca_gestiune(...)` primeste explicit `detalii_articol.cont` ca parametru
|
||||
(`ff_...:7485-7507`, apelat cand `nscadere_stoc = 1 AND id_gestiune <> -1000 AND in_stoc = 1`) —
|
||||
acesta e contul de gestiune/stoc (371/301-303/357 etc., documentat in raportul anterior),
|
||||
folosit pentru randul de descarcare de gestiune (marfa iesita din stoc), complet independent de
|
||||
`V_SCD`/`V_SCC` calculate mai sus pentru randul de venit al vanzarii. **Cele doua conturi
|
||||
coexista pe aceeasi linie de vanzare, cu surse complet diferite**: unul (gestiune) vine din
|
||||
`NOM_ARTICOLE.CONT`/`STOC.CONT`/`NOM_GESTIUNI.CONT` (cf. raport anterior), celalalt (venit) vine
|
||||
din `NOTE_CONTABILE.SCC` via politica de pret.
|
||||
- **Nu exista fallback/hardcodare** pentru `SCC`: daca `NOTE_CONTABILE` nu are un rand pentru
|
||||
`ID_SET`-ul politicii, `LEFT JOIN`-urile din `cursor_articol` produc `NULL` pe `SCC`/`SCD`
|
||||
(fara eroare explicita in acest cursor — spre deosebire de cazul `V_COMPUS`/politica lipsa la
|
||||
`ff_...:7293-7311`, care ridica `FACT-024`).
|
||||
|
||||
## 4. `ID_VENCHELT` / `NOM_VENIT_CHELTUIELI`
|
||||
|
||||
- **Nu are coloana de cont**: nicio `ALTER TABLE NOM_VENIT_CHELTUIELI ADD ... CONT` in
|
||||
`SCRIPTURI_CLAR` (cautare directa, zero rezultate) si nicio `CREATE TABLE NOM_VENIT_CHELTUIELI`
|
||||
in arhiva (tabelul predateaza 2009, inceputul arhivei `SCRIPTURI_CLAR` — la fel ca
|
||||
`NOTE_CONTABILE`, `CRM_NOTE_VANZARI`, `CRM_POLITICI_PRETURI`, pentru care nu exista `CREATE TABLE`
|
||||
in arhiva din acelasi motiv).
|
||||
- **Structura vazuta din uz**: `NOM_VENIT_CHELTUIELI.ID_VENCHELT%TYPE` (tip declarat repetat in
|
||||
`pack_facturare`, ex. `ff_...:191`) si `ACT.ID_VENCHELT%TYPE` (`ff_...:6725`) — deci
|
||||
`ID_VENCHELT` e o coloana FK pe `ACT` (tabelul de note contabile efective) si pe `CRM_NOTE_VANZARI`
|
||||
/ `CRM_POLITICI_PRET_ART` (vezi `NVL(B.ID_VENCHELT, D.ID_VENCHELT)` la `ff_...:7205`, unde `B` =
|
||||
`CRM_POLITICI_PRET_ART`, `D` = `CRM_NOTE_VANZARI` in cursorul comentat din pachet).
|
||||
- **Cine il consuma**: `pack_facturare.contabilizeaza_articol` il rezolva cu prioritate similara
|
||||
contului: `NVL(pack_facturare.nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT))` (override global de
|
||||
sesiune -> articol/politica -> nota de vanzare) si il trimite ca parametru propriu catre
|
||||
`scrie_nota(...)` (`ff_...:7461`), **separat** de `SCD`/`SCC`. E deci o **a treia dimensiune**
|
||||
(clasificare venituri/cheltuieli) atasata randului de nota contabila, nu sursa contului insusi —
|
||||
raportul anterior avea dreptate sa il caracterizeze ca "o clasificare, nu un cont", desi nu
|
||||
urmarise consumatorul pana la capat.
|
||||
- Numele `NOM_VENIT_CHELTUIELI` si folosirea alaturi de `SCD`/`SCC`/`ID_SECTIE` sugereaza un
|
||||
centru de venituri/cheltuieli pentru raportare (posibil situatii financiare pe sectii/centre de
|
||||
cost), nu un mecanism alternativ de determinare a contului 7xx.
|
||||
|
||||
## Neverificat
|
||||
|
||||
- **Structura completa (toate coloanele) a `NOTE_CONTABILE`, `CRM_NOTE_VANZARI`,
|
||||
`CRM_POLITICI_PRETURI` si `NOM_VENIT_CHELTUIELI`**: niciuna nu are `CREATE TABLE` in
|
||||
`SCRIPTURI_CLAR` (arhiva incepe 2009, tabelele sunt mai vechi). Coloanele folosite in cod sunt
|
||||
confirmate (`SCD`/`ASCD`/`SCC`/`ASCC`/`EXPLICATIE`/`CU_TVA`/`IN_VALUTA`/`ID_SET` pe
|
||||
`NOTE_CONTABILE`; `ID_NOTA`/`ID_SET` pe `CRM_NOTE_VANZARI`; `ID_NOTA`/`ID_POL` pe
|
||||
`CRM_POLITICI_PRETURI`; `ID_VENCHELT` pe `NOM_VENIT_CHELTUIELI`), dar lista completa ar necesita
|
||||
interogarea schemei Oracle live (`DESC NOTE_CONTABILE` etc.) sau un export DDL mai vechi decat
|
||||
2009, in afara bugetului acestei cercetari.
|
||||
- **Continutul efectiv al `NOTE_CONTABILE.SCC`** pentru notele configurate curent (adica ce conturi
|
||||
707/704/706/708 sunt de fapt setate, si daca fiecare politica de pret are o nota configurata) —
|
||||
ar necesita interogarea datelor din schema Oracle live, nu doar codul static.
|
||||
- **Daca `contabilizeaza_articol` e chiar apelata pe fluxul principal de facturare** (nu doar din
|
||||
`scrie_aviz_retur`) — am dedus asta din `CASE ... ntip <= 20 ...` (factura normala tratata explicit
|
||||
in functie), dar nu am urmarit *toti* apelantii ei in fisier (fisierul are 17000+ linii; cautarea
|
||||
`pack_facturare.contabilizeaza_articol` ar trebui repetata exhaustiv daca se doreste certitudine
|
||||
completa pe toate punctele de intrare — facturare avans, deviz, retail etc.).
|
||||
- **Comportamentul cand `NOTE_CONTABILE`/`CRM_NOTE_VANZARI` lipsesc pentru o politica de pret**:
|
||||
cursorul foloseste `LEFT JOIN`, deci `SCD`/`SCC` ar ajunge `NULL` fara eroare explicita in acest
|
||||
punct — nu am verificat daca exista o validare ulterioara (la `scrie_nota` sau la commit-ul notei)
|
||||
care sa blocheze o nota cu cont `NULL`.
|
||||
800
docs/cercetare/coresp_cont_venchelt.md
Normal file
800
docs/cercetare/coresp_cont_venchelt.md
Normal file
@@ -0,0 +1,800 @@
|
||||
# CORESP_CONT_VENCHELT ca sursa de cont de venit pentru articole fara politica de pret (decizia 27)
|
||||
|
||||
Cercetare pe cod, read-only, fara modificari. Continua `cont_venit_articol_fara_politica.md` si
|
||||
`cont_venit_corespondente.md` (care stabilisera ca SCC vine azi din `NOTE_CONTABILE` prin lantul
|
||||
politicii de pret) si `nota_contabila_fara_politica.md` (care stabilise ca `contabilizeaza_articol`
|
||||
ridica FACT-024 si opreste tranzactia cand nu exista politica).
|
||||
|
||||
## 9. Intrebarea care putea rasturna tot: "in ROAACNPRO si pe factura din comanda/contract se adauga articole fara politica de preturi — de ce nu si aici?"
|
||||
|
||||
**Raspuns scurt, verificat pe cod: impresia e explicabila, dar niciunul din cele doua exemple nu e de
|
||||
fapt un articol fara politica trecut cu bine prin `contabilizeaza_articol`. FACT-024 ramane blocantul
|
||||
real — nu exista azi, in productie, niciun flux in care un articol ajunge la `contabilizeaza_articol`
|
||||
fara sa fie membru al unei politici si sa treaca fara eroare.**
|
||||
|
||||
### 9a) Ce verifica de fapt FACT-024 — apartenenta articolului la politica liniei, nu "id_pol e gol"
|
||||
|
||||
Blocul (`D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7278-7302`, citat integral
|
||||
la sectiunea 6) face:
|
||||
```sql
|
||||
SELECT COMPUS, ID_POL_ART
|
||||
INTO V_COMPUS, V_ID_POL_ART
|
||||
FROM VCRM_POLITICI_PRET_ART
|
||||
WHERE ID_ARTICOL = detalii_articol.id_articol
|
||||
AND ID_POL = detalii_articol.id_pol;
|
||||
```
|
||||
Mesajul confirma exact asta: `'Articolul ' || ... || ' nu este definit in politica de preturi ' || ...`
|
||||
— verifica **apartenenta articolului la politica `id_pol` care a ajuns pe linie**, nu doar "exista vreun
|
||||
`id_pol`". Insa `id_pol` **nu e derivat de Oracle** din client/contract/tip document — e parametru de
|
||||
intrare explicit (`V_ID_POL IN NUMBER`, `adauga_articol_factura`, `ff_...:4993`, citat integral la
|
||||
sectiunea 8a), trimis de VFP pe fiecare linie. Deci sunt **doua cauze distincte care duc la aceeasi
|
||||
eroare**:
|
||||
1. `id_pol` e NULL/gol pe linie (articolul a fost ales dintr-o cautare care nu are deloc coloana
|
||||
`id_pol` — cazul `caut_articol`, sectiunea 9d) — `NO_DATA_FOUND` trivial (`X = NULL` nu se potriveste
|
||||
niciodata).
|
||||
2. `id_pol` e o valoare reala, dar articolul **nu e** in lista acelei politici — la fel `NO_DATA_FOUND`,
|
||||
cu acelasi mesaj.
|
||||
Ambele cauze converg spre FACT-024; codul nu le distinge in eroare. Formularea din intrebarea lui
|
||||
Marius ("nu se cauta politica articolului, ci apartenenta articolului la politica documentului") e
|
||||
**partial corecta**: verificarea e de apartenenta, dar `id_pol` nu vine din "antetul documentului" ca
|
||||
o singura valoare fixa — vine per-linie, de la formularul de adaugare a articolului (sectiunea 9d).
|
||||
|
||||
### 9b) ROAACNPRO — importul Roris/Contracte NU trece deloc prin `contabilizeaza_articol`
|
||||
|
||||
Verificat direct in `D:\ROA\ROAACNPRO\Programe\proceduri_acnpro.prg` (read-only). Cautare
|
||||
`pack_facturare\.|pack_acn\.|pack_contafin\.` in tot fisierul — apelurile de la salvarea facturii
|
||||
(`factura_salvare_db`, `:3331-3467`) sunt:
|
||||
```
|
||||
proceduri_acnpro.prg:3332 lcSql = [begin pack_facturare.initializeaza_scriere_actrul(NULL,1); end;]
|
||||
proceduri_acnpro.prg:3343 lcSql = [begin pack_facturare.initializeaza_date_factura(...
|
||||
proceduri_acnpro.prg:3382 lcSql = [begin pack_facturare.adauga_articol_factura_deviz(...
|
||||
proceduri_acnpro.prg:3412 lcSql = [begin pack_facturare.scrie_in_vanzari(0,...
|
||||
proceduri_acnpro.prg:3430 lcSql = [begin pack_acn.salveaza_regdoc(?.id, ?.id_vanzare, ?.tip, ...
|
||||
```
|
||||
**Exact tiparul deja documentat pentru ROAAUTO "Alte servicii" in `nota_contabila_fara_politica.md`**:
|
||||
`adauga_articol_factura_deviz` (insereaza doar in `VANZARI_DETALII_TEMP`, fara `ID_POL` — semnatura
|
||||
n-are acest parametru) urmat de `scrie_in_vanzari` (copiaza in `VANZARI_DETALII`, **fara sa scrie vreo
|
||||
nota contabila de venit**) — **`contabilizeaza_articol` nu apare deloc** in acest fisier (cautare
|
||||
directa `contabilizeaza_articol` in `proceduri_acnpro.prg`: zero rezultate). Nota contabila a
|
||||
documentului ACN o scrie **`pack_acn.salveaza_regdoc`**, o procedura complet separata, specifica ACN,
|
||||
apelata cu parametri proprii (`?.id_articol, ?.id_locatia, ?.id_valuta, ?.id_client, ?.id_contract,
|
||||
?.pret, ?.curs, ?.cantitate, ?.valval, ?.tvaval, ?.totval, ...`) — **fara `id_pol`** in lista de
|
||||
parametri. Confirmat si de raportul deja existent `COMUN\docs\cercetare\import_roris_roaacnpro.md`
|
||||
(sectiunea 3): "**nicio procedura Oracle stocata (`pack_*`) nu e apelata in etapa de import** ...
|
||||
acestea sunt apelate abia la salvarea finala", si articolele/preturile vin din dialoage de calcul tarife
|
||||
proprii ACN (`frm_calcul_tranzit`, `vctr_articole2`), nu din `CRM_POLITICI_PRET_ART`.
|
||||
|
||||
**Concluzie 9b**: ROAACNPRO nu e un exemplu de "articol fara politica trecut cu bine prin
|
||||
`contabilizeaza_articol`" — e un produs care **nu foloseste deloc** acest mecanism de contabilizare
|
||||
pentru facturile lui. Impresia lui Marius e intemeiata pe experienta ("acolo merge fara sa aleg vreo
|
||||
politica"), dar explicatia e alta decat "FACT-024 se poate evita" — e ca **acel flux nu ruleaza deloc
|
||||
codul care ar putea da FACT-024**.
|
||||
|
||||
### 9c) Factura din comanda/contract in ROAFACTURARE — articolul "liber" e de fapt din lista de preturi
|
||||
|
||||
Deja cercetat, cu dovezi, in `docs\cercetare\retur_si_lista_preturi.md` sectiunea B (citit integral aici,
|
||||
nu doar rezumat). Punctul central (`retur_si_lista_preturi.md` sectiunea B7):
|
||||
- **Contract** (tip 2,6,26,52): `crsarticole` (grila principala de articole, folosita si pentru
|
||||
adaugare "libera") e populat de `pack_facturare.cursor_contract(...)`, care da **lista de preturi
|
||||
completa**, nerestrictionata la continutul contractului — "azi se poate deja adauga liber din lista de
|
||||
preturi pe un document contract".
|
||||
- **Comanda** (tip 3): `crsarticole` e populat de `cursor_comanda`, **doar** articolele comenzii — nu
|
||||
exista adaugare libera in fluxul normal (doar prin copiere de document, care re-adauga tot din
|
||||
`cursor_preturi`).
|
||||
|
||||
Verificat aici, suplimentar, ca `cursor_preturi` (folosit si de `cursor_contract`, structura identica)
|
||||
**selecteaza explicit `A.ID_POL`** ca coloana de output:
|
||||
```sql
|
||||
-- ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2162-2167 (cursor_preturi, ramura V_TIP = 45, tipic pentru toate ramurile CASE)
|
||||
OPEN V_CURSOR FOR
|
||||
SELECT rownum as id_c,
|
||||
B.ID_ARTICOL,
|
||||
NULL AS LOT,
|
||||
NULL as SERIE,
|
||||
A.ID_POL,
|
||||
...
|
||||
```
|
||||
si ca cursorul insusi porneste, la fiecare apel, prin `pack_facturare.completare_politica_stoc()`
|
||||
(`ff_...:2151`), care garanteaza deja randuri in `CRM_POLITICI_PRET_ART` pentru toate articolele
|
||||
`IN_STOC=1` (procedura citata integral la sectiunea 8c2). Deci **fiecare articol afisat in grila
|
||||
"lista de preturi"/"contract" vine deja cu un `id_pol` valid, dintr-o politica reala**, exact pentru ca
|
||||
e extras printr-un join pe `CRM_POLITICI_PRETURI`/`CRM_POLITICI_PRET_ART`. "Adaugare libera pe contract"
|
||||
nu inseamna "articol fara politica" — inseamna "orice articol din lista de preturi, nu doar cele din
|
||||
contract", dar tot cu politica ceruta, doar aleasa implicit prin cursor, nu prin dialogul manual
|
||||
`do_cauta_politica`.
|
||||
|
||||
**Concluzie 9c**: nu e un contraexemplu la FACT-024 — e acelasi mecanism (articol + politica), livrat
|
||||
printr-o alta grila de selectie (`crsarticole` populat de `cursor_contract` in loc de `caut_articol`),
|
||||
care intampla sa nu ceara utilizatorului sa caute manual politica pentru ca cursorul o aduce deja
|
||||
atasata pe fiecare rand.
|
||||
|
||||
### 9d) Ce diferentiaza de fapt "adaugare din nomenclator" (cazul #13) de "adaugare din lista de preturi/contract" (cazul care merge azi)
|
||||
|
||||
Diferenta e cursorul sursa al gridului de articole, nu tipul de document:
|
||||
- **`caut_articol`** (`COMUN\programe\ocautare.prg:1636-1735`, folosit pentru cautare libera in
|
||||
nomenclator) — SELECT-ul confirmat la `ocautare.prg:1671-1689` (`vnom_articole_crm`/`vnom_articole`):
|
||||
**nu are coloana `id_pol`** in nicio ramura (`tlCRM` sau nu). Un articol ales prin acest dialog ajunge
|
||||
in `poArticol` (scatter din `crsarticole`, `ofacturare.vc2:12850-12851`) **fara `id_pol`** — exact
|
||||
cazul care da FACT-024 azi, si exact cazul cerut de povestea #13 ("adaugat direct din nomenclator").
|
||||
- **`cursor_preturi`/`cursor_contract`** (Oracle, sectiunea 9c) — id_pol e parte din rezultat, pentru ca
|
||||
interogarea porneste de la politica de pret, nu de la nomenclator.
|
||||
|
||||
Deci povestea #13 cere exact ce nu exista azi: un articol ales **prin cautarea de nomenclator**
|
||||
(fara trecere prin vreo politica), caruia i se cere totusi sa treaca prin `contabilizeaza_articol` fara
|
||||
eroare. Niciunul din exemplele lui Marius (ROAACNPRO, contract) nu demonstreaza ca asta functioneaza deja
|
||||
undeva — primul nu foloseste deloc functia, al doilea nu e de fapt "fara politica".
|
||||
|
||||
### Verdict 9
|
||||
|
||||
**FACT-024 ramane blocantul real**, confirmand (nu contrazicand) analiza din sectiunile 6-8. Nu exista
|
||||
azi, pe niciun flux de productie cercetat, un caz in care un articol trece prin
|
||||
`pack_facturare.contabilizeaza_articol` fara sa fie membru al unei politici de pret si nu cade cu
|
||||
eroare. Impresia lui Marius e intemeiata pe doua experiente reale, dar explicatia lor e alta:
|
||||
- ROAACNPRO: alt produs, alta procedura de contabilizare (`pack_acn.salveaza_regdoc`), niciodata
|
||||
`contabilizeaza_articol`;
|
||||
- Contract in ROAFACTURARE: articolul PARE liber, dar e adus printr-un cursor care il livreaza deja cu
|
||||
o politica reala atasata (`cursor_contract`/`cursor_preturi` + `completare_politica_stoc`).
|
||||
|
||||
Asta nu inseamna ca planul A (sectiunea 8, VFP alege singur un `id_pol`) sau planul B (sectiunea 6,
|
||||
fallback in pachet) sunt de abandonat — arata insa ca **niciuna din cele doua nu se poate simplifica
|
||||
la "nu faceti nimic, functioneaza deja"**: mecanismul de protectie e real si consecvent, nu un artefact
|
||||
uitat.
|
||||
|
||||
## Constrangere noua (Marius, aparuta in timpul cercetarii): pack_facturare e plan B
|
||||
|
||||
Marius prefera sa **nu se scrie cod nou in `pack_facturare`** — e pachetul comun folosit de toata
|
||||
suita, si orice ramura noua acolo e risc peste tot, nu doar in ROAFACTURARE. Sectiunea 6 (punctul de
|
||||
injectie in pachet) ramane in raport ca informatie, dar devine **plan B**. Intrebarea reala, tratata in
|
||||
sectiunea 8 de mai jos: **se poate obtine acelasi rezultat din VFP, alegand singur un `id_pol` potrivit,
|
||||
fara sa se modifice `pack_facturare`?** Raspuns scurt: **da, ingredientele exista deja**, dar solutia nu
|
||||
e "zero cod" — e cod VFP nou (nu Oracle), care se sprijina pe 3 mecanisme deja functionale in productie.
|
||||
Detalii in sectiunea 8.
|
||||
|
||||
## Concluzie: fezabil cu conditii, si conditiile sunt mai mari decat pare din formularea deciziei 27
|
||||
|
||||
0. **Verificare prioritara (sectiunea 9, ceruta de Marius in timpul cercetarii): FACT-024 ramane
|
||||
blocantul real.** Nu exista azi niciun flux de productie in care un articol trece prin
|
||||
`contabilizeaza_articol` fara sa fie membru al unei politici de pret si nu cade cu eroare.
|
||||
ROAACNPRO nu e un contraexemplu — nu foloseste deloc `contabilizeaza_articol` (alta procedura de
|
||||
contabilizare, `pack_acn.salveaza_regdoc`). Contractul in ROAFACTURARE nu e un contraexemplu —
|
||||
articolele "libere" vin din `cursor_contract`/`cursor_preturi`, care le ataseaza deja o politica
|
||||
reala. Detalii complete in sectiunea 9.
|
||||
1. **Tabelul exista, e populat cu date reale si e folosit activ in productie** — dar niciodata pentru
|
||||
`CONT_VENIT`. Toti consumatorii gasiti (pack_vin, pack_devize, gestiune_pack_gest_import,
|
||||
gestiune_rapoarte) citesc `CONT_CHELT` (sau `CONT_APROVIZIONARE`), niciunul `CONT_VENIT`. Coloana
|
||||
`CONT_VENIT` e completata cu date reale (o migrare din 2023 acopera 20 de conturi de gestiune), dar
|
||||
n-are niciun consumator — nici Oracle, nici VFP. Cititul ei pentru facturare ar fi un consumator nou,
|
||||
nu o reteta deja rulata in productie.
|
||||
2. **Cheia de join e stabila si testata**: mereu `CONT` (contul de gestiune al liniei), cu filtru
|
||||
`STERS = 0`, prin `LEFT JOIN`. Pattern-ul se poate copia identic pentru `CONT_VENIT`.
|
||||
3. **Punctul care schimba verdictul de la "fezabil simplu" la "fezabil cu conditii mari"**: in
|
||||
`contabilizeaza_articol`, ramura "articol simplu" nu executa **nimic** (nici `scrie_nota`, nici
|
||||
`descarca_gestiune`) daca `cursor_articol` (care citeste din `CRM_POLITICI_PRET_ART`) nu gaseste
|
||||
niciun rand — si nu gaseste niciun rand exact in cazurile in care azi se ridica FACT-024. Un simplu
|
||||
"prinde exceptia si continua" ar produce o linie facturata **fara nota contabila si fara descarcare
|
||||
de gestiune, fara nicio eroare** — mai rau decat blocajul actual. Fallback-ul cere o **ramura noua,
|
||||
paralela cursorului**, nu doar inlocuirea unei valori.
|
||||
4. **Chiar cu ramura noua, `CU_TVA` si `IN_VALUTA` (cota TVA inclusa in pret / linie in valuta) nu au
|
||||
nicio sursa alternativa** in afara `NOTE_CONTABILE`. Corespondentele si `NOM_ARTICOLE.CONT` dau doar
|
||||
un cont, nu aceste doua semnale de interpretare a pretului — ele trebuie fie hardcodate (risc de
|
||||
calcul gresit al bazei/TVA), fie cerute ca intrare noua de la utilizator/articol.
|
||||
5. **704 ca literal de fallback are un precedent real in suita**, dar in alt subsistem: proprietatea
|
||||
`cconte = 704` ("cont implicit articol client") din `frm_configurare_efactura`
|
||||
(`COMUN\clase\anaf_efactura.vc2:8376`), folosita la import eFactura, nu in `pack_facturare`. Nu e o
|
||||
reteta gata scrisa pentru facturare, dar arata ca alegerea lui Marius nu e arbitrara in suita.
|
||||
|
||||
## 1. `CORESP_CONT_VENCHELT` exista? Structura, DDL, consumatori
|
||||
|
||||
**Exista.** Nu are `CREATE TABLE` in arhiva `D:\ROA\DATABASE\SCRIPTURI_CLAR` (arhiva incepe 2009-2010,
|
||||
tabelul e mai vechi — acelasi motiv pentru care `NOTE_CONTABILE`/`CRM_NOTE_VANZARI` nu au `CREATE TABLE`
|
||||
in arhiva, documentat deja in `cont_venit_corespondente.md`).
|
||||
|
||||
**Coloane confirmate din `ALTER TABLE` + `CREATE OR REPLACE VIEW` (cronologic):**
|
||||
- `CONT`, `CONT_CHELT`, `CONT_VENIT`, `STERS` — preexistente arhivei (folosite direct in view-ul din
|
||||
2010 fara `ALTER TABLE` premergator vizibil).
|
||||
- `CONT_APROVIZIONARE varchar2(4)` — adaugata 19.02.2010
|
||||
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2010\02\ff_2010_02_19_03_GESTIUNI.sql:9`:
|
||||
`alter table CORESP_CONT_VENCHELT add CONT_APROVIZIONARE varchar2(4);`).
|
||||
- `CONT_DIFERENTE varchar2(4)` — adaugata 05.02.2015
|
||||
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2015\02\ff_2015_02_05_01_GESTIUNE.sql:6-7`:
|
||||
`alter table coresp_cont_venchelt add cont_diferente varchar2(4);` +
|
||||
`comment on column CORESP_CONT_VENCHELT.cont_diferente is 'cont diferente de pret pe NIR fata de
|
||||
pretul din factura de achizitie (ex: 6588 = 401 sau 308 = 401)';`).
|
||||
- Nu am gasit `ID_CCV`/`DATAORAS` in DDL-ul cercetat (cautare directa, zero rezultate in
|
||||
`SCRIPTURI_CLAR`) — interogarea din decizia 27 (`select id_ccv, cont, cont_chelt, cont_venit, sters,
|
||||
dataoras, cont_aprovizionare, cont_diferente from coresp_cont_venchelt`) le presupune, dar tabelul
|
||||
fiind pre-arhiva, DDL-ul complet nu e in cod static. **Ramane de verificat pe baza vie** (vezi sectiunea
|
||||
finala) — probabil `ID_CCV` e cheia surogat (PK) si `DATAORAS` un timestamp tehnic, tipar comun in
|
||||
suita ROA pentru tabelele de configurare, dar neconfirmat din DDL.
|
||||
- Toate coloanele `CONT*` sunt `varchar2(4)` (confirmat din `ALTER TABLE` explicit pentru
|
||||
`CONT_APROVIZIONARE`/`CONT_DIFERENTE`; consistent cu tratarea lor ca cod de cont in tot codul citit).
|
||||
|
||||
**View-ul consumat de VFP**, `VCORESP_CONT_VENCHELT`, filtreaza deja `STERS = 0` (deci VFP nu mai trebuie
|
||||
sa filtreze separat):
|
||||
```sql
|
||||
-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2015\02\ff_2015_02_05_01_GESTIUNE.sql:10-13
|
||||
create or replace view vcoresp_cont_venchelt as
|
||||
select cont, cont_chelt, cont_venit, cont_aprovizionare, cont_diferente
|
||||
from coresp_cont_venchelt
|
||||
where sters = 0;
|
||||
```
|
||||
|
||||
**Populare cu date reale — `CONT_VENIT` e completat, nu doar declarat.** Migrare dedicata 23.02.2023
|
||||
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2023\02\ff_2023_02_23_01_COMUN.sql:1-21`, titlul chiar din fisier:
|
||||
`-- Completare cont venit pe corespondenta 3xx - 6xx/7xx`):
|
||||
```sql
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '301';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '302';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '3021';
|
||||
... (3022,3023,3024,3025,3026,3028, 303 -> tot '707')
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '711' where cont = '331';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '711' where cont = '332';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '702' where cont = '341';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '7015' where cont = '345';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '703' where cont = '346';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '7015' where cont = '348';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '7018' where cont = '361';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '371';
|
||||
update CORESP_CONT_VENCHELT set cont_venit = '707' where cont = '381';
|
||||
```
|
||||
Deci pentru conturile de gestiune uzuale (marfuri 371/381, materii prime 301-303/3021-3028, produse
|
||||
finite 345/348, semifabricate 341, animale 346, ambalaje 381) exista deja o valoare `CONT_VENIT`
|
||||
configurata in Oracle, de 3 ani. **Datele exista** — intrebarea e doar cine le citeste.
|
||||
|
||||
**Ecran de configurare in VFP: NU exista.** Cautare exhaustiva `CORESP_CONT_VENCHELT` (case-insensitive)
|
||||
in `D:\ROA\ROAFACTURARE\COMUN` -> exact 3 fisiere, niciunul un formular de editare:
|
||||
- `COMUN\clase\ointroduceri.vc2:2` — un comentariu de istoric (19.02.2010, mentioneaza
|
||||
`crsconfigcvc (coresp_cont_venchelt)`), nu cod.
|
||||
- `COMUN\programe\ointroduceri.prg:733` (procedura `update_corespondente_cvc`, citata integral mai jos)
|
||||
— un `SELECT` care **reincarca** un cursor local, nu scrie in tabel.
|
||||
- `COMUN\programe\updateserver.prg:733` — acelasi `SELECT`, alt loc de apel.
|
||||
|
||||
```
|
||||
-- COMUN\programe\ointroduceri.prg:727-743
|
||||
Procedure update_corespondente_cvc
|
||||
If Used('crsconfigcvc')
|
||||
Use In crsconfigcvc
|
||||
Endif
|
||||
*!* 19.02.2010
|
||||
lcSql = [select cont,cont_chelt,cont_venit, cont_aprovizionare, cont_diferente from vcoresp_cont_venchelt order by cont]
|
||||
*!* 19.02.2010 ^
|
||||
lnSucces = goExecutor.oExecute(lcSql,[crsconfigcvc])
|
||||
If lnSucces < 0
|
||||
amessagebox(goExecutor.cEroare,16,"Eroare")
|
||||
Endif
|
||||
goExecutor.oReset()
|
||||
Return lnSucces
|
||||
Endproc
|
||||
```
|
||||
|
||||
Deci tabelul e intretinut exclusiv prin **scripturi SQL manuale** (cele din `SCRIPTURI_CLAR`, rulate de un
|
||||
DBA/dezvoltator la nevoie), nu printr-un formular VFP — la fel ca alte tabele tehnice de configurare mai
|
||||
vechi din suita. Nu exista niciun `INSERT INTO CORESP_CONT_VENCHELT` in arhiva (randurile de baza,
|
||||
`CONT`/`CONT_CHELT`/`CONT_VENIT`, predateaza arhiva); doar `UPDATE`/`ALTER TABLE` pentru coloanele
|
||||
adaugate ulterior (`CONT_APROVIZIONARE` in 2010, `CONT_DIFERENTE` in 2015, valorile `CONT_VENIT` in
|
||||
2023).
|
||||
|
||||
## 2. Cheia de cautare
|
||||
|
||||
**`CONT` = contul de gestiune al liniei, mereu.** Confirmat prin 4 exemple reale de `JOIN`, in module
|
||||
diferite:
|
||||
|
||||
- **pack_vin** (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2023\07\ff_2023_07_18_02_VIN_PACK_VIN.sql:1579-1589`,
|
||||
procedura `inregistreaza_materiale`):
|
||||
```sql
|
||||
FROM (SELECT SUM(...) as suma, A.CONT as SCC, A.ACONT as ASCC, A.ID_GESTIUNE
|
||||
FROM VIN_RULAJE_TEMP A WHERE A.TIP = V_TIP
|
||||
GROUP BY A.ID_GESTIUNE, A.CONT, A.ACONT) A
|
||||
LEFT JOIN CORESP_CONT_VENCHELT B
|
||||
ON A.SCC = B.CONT
|
||||
AND B.STERS = 0;
|
||||
```
|
||||
aici `A.SCC` e de fapt contul de gestiune (aliasul `SCC` vine din contextul rulajului de stoc, nu din
|
||||
`NOTE_CONTABILE`) — deci join-ul e tot `cont_gestiune = B.CONT`.
|
||||
- **pack_devize** (14+ aparitii identice intre 2013-2014, ex.
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2014\01\ff_2014_01_22_01_DEVIZE_PACK_DEVIZE.sql:3214-3217`):
|
||||
```sql
|
||||
from rul_temp a
|
||||
left join coresp_cont_venchelt b
|
||||
on a.cont = b.cont
|
||||
and b.sters = 0
|
||||
```
|
||||
`rul_temp.cont` e contul de gestiune al articolului din rulajul de stoc.
|
||||
- **gestiune_pack_gest_import** (5+ aparitii 2013-2017, ex.
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2017\05\ff_2017_05_31_01_GESTIUNE_PACK_GEST_IMPORT.sql:512`):
|
||||
`left join coresp_cont_venchelt b` — pe `a.cont = b.cont` (acelasi tipar).
|
||||
- **Raport recent, 2026** (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\07\ff_2026_07_23_01_GESTIUNE_RAPOARTE.sql:25-29`,
|
||||
view `VRUL_ACT_CHELTUIELI`):
|
||||
```sql
|
||||
iesiri AS (
|
||||
SELECT R.*, CC.CONT_CHELT AS CONT_CHELT_CORESP
|
||||
FROM VRUL_TOT R
|
||||
LEFT JOIN CORESP_CONT_VENCHELT CC ON CC.CONT = R.CONT AND CC.STERS = 0
|
||||
WHERE R.STERS = 0 AND R.CANTE <> 0
|
||||
)
|
||||
```
|
||||
cel mai recent consumator gasit (23.07.2026), deci tabelul e inca "viu" in productie — dar
|
||||
`CONT_CHELT_CORESP` calculat aici nici nu apare in `SELECT`-ul final al view-ului (e calculat si
|
||||
abandonat), semn ca tiparul de "join pe CONT, ia campul de care ai nevoie" e suficient de standard
|
||||
incat sa fie reutilizat fara alta discutie de design.
|
||||
- **In VFP**: `Select crsconfigcvc; Locate For Cont = Upper(Alltrim(...))` — vezi sectiunea 3, mereu
|
||||
aceeasi cheie `CONT`.
|
||||
|
||||
**Nu am gasit nicio discriminare suplimentara pe gestiune/tip document.** Toate `JOIN`-urile/`LOCATE`
|
||||
sunt strict pe `CONT` (+ `STERS = 0`), fara `ID_GESTIUNE`, fara `TIP_DOC`. `ID_CCV` nu apare folosit ca
|
||||
FK in niciun cod cercetat — pare o simpla cheie surogata a tabelului, neexpusa in `VCORESP_CONT_VENCHELT`
|
||||
(view-ul nu o include).
|
||||
|
||||
**Ambiguitate / mai multe randuri active pentru acelasi `CONT`**: nu exista `UNIQUE`/`PRIMARY KEY`
|
||||
vizibil in cod (DDL-ul e pre-arhiva), dar **toate cele ~20 de utilizari reale presupun un singur rand
|
||||
activ per `CONT`** — niciun cod cercetat foloseste `DISTINCT`, `ROW_NUMBER()`, `MAX(...)` sau alt
|
||||
mecanism de dezambiguizare pe rezultatul join-ului. Daca ar exista doua randuri active cu acelasi `CONT`,
|
||||
`LEFT JOIN`-ul ar multiplica randurile din interogarea principala (risc de dublare de suma in
|
||||
`pack_vin`/`pack_devize`) — nu exista protectie in cod contra acestui caz. **Presupunerea de unicitate e
|
||||
implicita in tot codul care exista deja**, nu doar in propunerea lui Marius.
|
||||
|
||||
## 3. Cine il foloseste azi — CONT_CHELT are 4 module consumatoare, CONT_VENIT are zero
|
||||
|
||||
| Consumator | Fisier | Coloana citita |
|
||||
|---|---|---|
|
||||
| `pack_vin.inregistreaza_materiale` | `ff_2023_07_18_02_VIN_PACK_VIN.sql:1567` (si variante 2010/2012/2014) | `CONT_CHELT` |
|
||||
| `pack_devize` (procedura de calcul cost material, 14+ export-uri 2013-2014) | `ff_2014_01_22_01_DEVIZE_PACK_DEVIZE.sql:3222` (si altele) | `CONT_CHELT` (in `GROUP BY`, folosit ca `SCD`) |
|
||||
| `pack_gest_import` (5+ export-uri 2013-2017) | `ff_2017_05_31_01_GESTIUNE_PACK_GEST_IMPORT.sql:512` | `CONT_CHELT` (deductie din context, tipar identic) |
|
||||
| `VRUL_ACT_CHELTUIELI` (raport, 2026) | `ff_2026_07_23_01_GESTIUNE_RAPOARTE.sql:26` | `CONT_CHELT` (calculat, needatat in output final) |
|
||||
| VFP `ointroduceri.vc2` (bon consum, finalizare aprovizionare, transfer) | `:5476-5490` (`oinventar.vc2`), `:14663-14681`, `:15327-15332` | `CONT_CHELT`, `CONT_APROVIZIONARE` |
|
||||
| VFP `ointroduceri.vc2` (diferente NIR vs factura) | `:15395-15403` (bloc dezactivat `IF .F.`) | `CONT_DIFERENTE` (cod comentat/mort, nu ruleaza azi) |
|
||||
|
||||
**Niciun consumator pentru `CONT_VENIT`** — cautare directa `CONT_VENIT` (case-insensitive) in tot
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR` (arhiva completa 2009-2026): doar 3 fisiere, toate deja citate (2 view-uri
|
||||
care includ coloana in `SELECT` fara sa o consume, 1 script de populare cu date). In VFP, cautare
|
||||
`cont_venit` (case-insensitive) in `COMUN` si `ROAGEST`: un singur hit, `SELECT ... cont_venit ...` in
|
||||
`update_corespondente_cvc`/`updateserver.prg` (fetch in cursor), **zero utilizari ale campului dupa
|
||||
fetch** (nicio referinta `.cont_venit`/`crsconfigcvc.cont_venit` gasita).
|
||||
|
||||
**Concluzie**: propunerea lui Marius ar fi **primul consumator real al `CONT_VENIT`** in toata suita,
|
||||
desi coloana e populata din 2023. Reteta "citeste corespondenta pe cont de gestiune" e deja rulata in
|
||||
productie de 10+ ani, dar mereu pentru latura `CONT_CHELT` (cheltuiala la consum de stoc), niciodata
|
||||
pentru `CONT_VENIT` (venit la vanzare). Riscul tehnic al join-ului (cheie, filtru `STERS`, tipul
|
||||
`LEFT JOIN`) e deja validat de utilizarea existenta; riscul de **date** (daca valorile `CONT_VENIT`
|
||||
completate in 2023 sunt corecte/complete pentru toate conturile de gestiune folosite azi in facturare,
|
||||
nu doar cele 20 acoperite explicit) e nou si neverificat pe baza vie.
|
||||
|
||||
## 4. Semantica coloanelor
|
||||
|
||||
Dedusa din utilizari reale, nu din nume:
|
||||
|
||||
- **`CONT`**: contul de gestiune/stoc al liniei (clasa 3xx: 301-303, 331/332, 341, 345/346/348, 361,
|
||||
371, 381) — cheia de cautare in toate cazurile vazute (sectiunea 2).
|
||||
- **`CONT_CHELT`**: contul de cheltuiala (6xx) care se debiteaza cand marfa/materialul iese din gestiune
|
||||
(consum, bon de consum, productie) — confirmat de toti cei 4+ consumatori din sectiunea 3, unde e
|
||||
folosit mereu ca `SCD` (debit) pe o nota de tip "iesire din gestiune".
|
||||
- **`CONT_VENIT`**: prin simetrie de nume si migrarea din 2023 ("cont venit pe corespondenta 3xx-7xx"),
|
||||
e menit sa fie contul de venit (7xx) corespunzator vanzarii aceluiasi cont de gestiune — dar **nu are
|
||||
niciun consumator care sa confirme comportamentul in executie** (nicio ramura de cod care sa arate ce
|
||||
se intampla la `NULL`, la vanzare partiala, la retur). Semantica e dedusa din nume + date, nu din
|
||||
utilizare, contrar regulii "nu presupune semantica dupa nume" — de aceea marchez ca **ipoteza, nu
|
||||
fapt verificat pe comportament**.
|
||||
- **`CONT_APROVIZIONARE`**: contul folosit la operatia "finalizare aprovizionare" (`ID_SET` 264/265),
|
||||
cand se transfera valoarea din contul de tranzit (401 sau alt cont de gestiune) intr-un cont de
|
||||
gestiune specific de tranzit intern (32x) — confirmat de comentariul din DDL 2010 si de utilizarea in
|
||||
`ointroduceri.vc2:15323-15332` (`Case Alltrim(Upper(oSet.explicatia)) = 'FINALIZARE APROVIZIONARE' ...
|
||||
lcContC = Alltrim(cont_aprovizionare)`).
|
||||
- **`CONT_DIFERENTE`**: contul folosit pentru diferentele de pret intre NIR si factura de achizitie —
|
||||
confirmat de comentariul DDL 2015 ("cont diferente de pret pe NIR fata de pretul din factura de
|
||||
achizitie, ex: 6588 = 401 sau 308 = 401") si de codul `ointroduceri.vc2:15394-15403`, dar acel bloc e
|
||||
**dezactivat** (`IF .F. THEN ... ENDIF`), deci in executia curenta ramane mereu fallback-ul hardcodat
|
||||
`'6588'` — dovada ca chiar si un camp cu semantica clara si comentariu explicit poate ajunge neutilizat
|
||||
in productie, ceea ce intareste incertitudinea despre `CONT_VENIT`.
|
||||
- **`STERS`**: flag boolean soft-delete, filtrat explicit `= 0` in **toate** utilizarile gasite (view-ul
|
||||
`VCORESP_CONT_VENCHELT` il filtreaza deja la nivel de view; join-urile Oracle directe pe tabel il
|
||||
filtreaza explicit `AND B.STERS = 0`/`AND CC.STERS = 0`). Niciun cod cercetat citeste randuri cu
|
||||
`STERS = 1`.
|
||||
- **`DATAORAS`**: nu apare in niciun cod cercetat (Oracle sau VFP) — nu am gasit nicio referinta directa
|
||||
la aceasta coloana in afara interogarii propuse in decizia 27. Tipic in suita ROA e un timestamp
|
||||
tehnic de audit (data ultimei modificari a randului), dar **neconfirmat aici** — ramane de verificat pe
|
||||
schema vie.
|
||||
- **Ambiguitate pe `CONT`**: vezi sectiunea 2 — niciun cod nu trateaza cazul "mai multe randuri active
|
||||
pentru acelasi CONT"; presupunerea de unicitate e implicita, nu impusa de o constrangere vizibila.
|
||||
|
||||
## 5. `NOM_ARTICOLE.CONT` pentru articole negestionabile si fallback pe 704
|
||||
|
||||
- **`NOM_ARTICOLE.CONT` nu e restrans la clasa 3** — reconfirmat (vezi deja `cont_venit_corespondente.md`
|
||||
sectiunea 2): validarea `verific_cont` (`COMUN\programe\oproceduri_comune.prg:2389-2415`) verifica doar
|
||||
ca contul exista in planul de conturi al anului, fara filtru de clasa; niciun `CHECK CONSTRAINT` in
|
||||
DDL. Deci campul poate contine legitim un cont 6xx/7xx pentru un articol negestionabil, exact cum
|
||||
presupune decizia 27.
|
||||
- **Niciun fallback pe `704` existent in `pack_facturare`** — cautare `'704'` (literal SQL) in
|
||||
`D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17000+ linii, exportul curent al
|
||||
pachetului): **zero rezultate**. Cautare in tot `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026` (toate scripturile
|
||||
din 2026): **zero rezultate**. Fallback-ul pe 704 propus de Marius ar fi cod nou, nu o reteta deja
|
||||
scrisa in `pack_facturare`.
|
||||
- **Exista totusi un precedent independent pentru 704 ca "cont implicit de venit"**, in alt subsistem:
|
||||
```
|
||||
COMUN\clase\anaf_efactura.vc2:8370-8376 (clasa "frm_configurare_efactura")
|
||||
*p: cconte && cont implicit articol client
|
||||
*p: ccontp && cont implicit articol furnizor
|
||||
...
|
||||
cconte = 704
|
||||
ccontp = 628
|
||||
```
|
||||
E o proprietate cu valoare implicita `704` ("cont implicit articol client", deci un cont de venit
|
||||
implicit pentru liniile generate la import eFactura) intr-un ecran de configurare a modulului ANAF
|
||||
eFactura. **Nu am gasit cod care sa citeasca explicit `.cconte`** (cautare `\.cconte\b` in tot `COMUN`:
|
||||
zero rezultate) — proprietatea e definita cu valoare implicita, dar consumatorul ei concret n-a fost
|
||||
gasit in timpul alocat (posibil legat generic la un camp de configurare cu acelasi nume, tipar comun in
|
||||
suita, dar neconfirmat aici). **Ce arata cu certitudine**: alegerea `704` ca implicit pentru "cont
|
||||
articol client" nu e o inventie a acestei cereri — a mai fost aleasa o data, independent, de altcineva
|
||||
din echipa (sau de Marius insusi, in alt moment), pentru un scop asemanator. Nu e insa o reteta de cod
|
||||
reutilizabila direct in `pack_facturare` — e in alt pachet/clasa, alt flux (import, nu emitere).
|
||||
|
||||
## 6. Punctul de injectie in `contabilizeaza_articol`
|
||||
|
||||
Sursa: `D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (exportul curent al
|
||||
pachetului, 17548+ linii). Numerotarea de mai jos e din **acest fisier**, verificata prin citire directa
|
||||
(difera cu cateva linii fata de exportul mai vechi citat in rapoartele anterioare, care avea FACT-024 la
|
||||
7301 in loc de 7311 — normal, fisierul a mai fost actualizat intre timp).
|
||||
|
||||
**Structura exacta a blocului care ridica FACT-024** (`:7275-7302`, inceputul functiei):
|
||||
```
|
||||
7275 BEGIN
|
||||
7276
|
||||
7277 -- 05.07.2011
|
||||
7278 BEGIN
|
||||
7279 SELECT COMPUS, ID_POL_ART
|
||||
7280 INTO V_COMPUS, V_ID_POL_ART
|
||||
7281 FROM VCRM_POLITICI_PRET_ART
|
||||
7282 WHERE ID_ARTICOL = detalii_articol.id_articol
|
||||
7283 AND ID_POL = detalii_articol.id_pol;
|
||||
7284 EXCEPTION
|
||||
7285 WHEN NO_DATA_FOUND THEN
|
||||
7286 SELECT DENUMIRE
|
||||
7287 INTO lcArticol
|
||||
7288 FROM NOM_ARTICOLE
|
||||
7289 where id_articol = detalii_articol.id_articol;
|
||||
7290
|
||||
7291 SELECT NUME_LISTA_PRETURI
|
||||
7292 INTO lcPolitica
|
||||
7293 FROM CRM_POLITICI_PRETURI
|
||||
7294 where id_pol = detalii_articol.id_pol;
|
||||
7295
|
||||
7296 RAISE_APPLICATION_ERROR(-20000,
|
||||
7297 'Articolul ' || detalii_articol.id_articol || '|' ||
|
||||
7298 lcArticol ||
|
||||
7299 ' nu este definit in politica de preturi ' ||
|
||||
7300 detalii_articol.id_pol || '|' || lcPolitica ||
|
||||
7301 '! (FACT-024)');
|
||||
7302 END;
|
||||
```
|
||||
|
||||
**Acesta e locul unde s-ar decide "articolul nu are politica" — dar NU e suficient sa se schimbe doar
|
||||
acest bloc.** Motivul, cu citate exacte:
|
||||
|
||||
1. Dupa acest bloc, codul verifica `IF V_COMPUS = 1 THEN` (articol compus, `:7305`, ridica alta eroare,
|
||||
FACT-016) `ELSE` (`:7391`) — ramura "articol simplu" care e cea relevanta pentru cazul cerut.
|
||||
2. Ramura "articol simplu" **deschide un cursor nou**, `cursor_articol` (definit la `:7218-7271`),
|
||||
filtrat exact pe aceeasi cheie care a dat `NO_DATA_FOUND` mai sus:
|
||||
```
|
||||
7261 FROM CRM_POLITICI_PRET_ART A
|
||||
...
|
||||
7268 WHERE
|
||||
7269 -- A.STERS = 0 AND
|
||||
7270 A.ID_POL = detalii_articol.id_pol
|
||||
7271 AND A.ID_ARTICOL = detalii_articol.id_articol;
|
||||
```
|
||||
3. Tot corpul de lucru real (calculul `V_SCD`/`V_ASCD`/`V_SCC`/`V_ASCC`/`V_EXPLICATIE`, apelul
|
||||
`pack_facturare.scrie_nota(...)` la `:7443-7467`, apelul `pack_facturare.descarca_gestiune(...)` la
|
||||
`:7476-7498`, scrierea discountului la `:7501-7518`) e **in interiorul**
|
||||
`WHILE cursor_articol%FOUND LOOP` (`:7396-7541`). **Daca articolul nu are politica de pret, acest
|
||||
cursor nu returneaza niciun rand** (e exact aceeasi conditie care a dat `NO_DATA_FOUND` la pasul 1,
|
||||
pe acelasi `(id_pol, id_articol)`), deci bucla ruleaza zero iteratii.
|
||||
|
||||
**Concluzia tehnica**: daca s-ar modifica *doar* blocul `EXCEPTION WHEN NO_DATA_FOUND` (pasul 1) ca sa nu
|
||||
mai ridice `RAISE_APPLICATION_ERROR` si sa lase `V_COMPUS := 0` implicit, executia ar trece la ramura
|
||||
"articol simplu", ar deschide `cursor_articol`, acesta ar gasi tot zero randuri (aceeasi cauza), bucla nu
|
||||
ar rula deloc, si functia ar reveni `RETURN V_INCASAT_CALCUL` cu valoarea initiala `0`
|
||||
(declarata la `:7178`), **fara sa scrie nicio nota contabila si fara sa descarce gestiunea** — silentios,
|
||||
fara nicio eroare. Asta e o regresie mai grava decat FACT-024: azi tranzactia se opreste vizibil; cu un
|
||||
fallback naiv, factura s-ar emite fara inregistrare contabila si fara descarcare de stoc, nedetectabil
|
||||
fara audit manual.
|
||||
|
||||
**Ce ar cere de fapt fallback-ul, pentru a pastra comportamentul actual cand politica exista**:
|
||||
- Blocul de la pasul 1 ar trebui sa **distinga** explicit cazul "politica lipseste" (continua cu
|
||||
fallback) de cazul "articol compus fara politica" (probabil tot eroare, nu e in scopul cerut) —
|
||||
posibil punand un flag local (`V_ARE_POLITICA := FALSE`) in loc de `RAISE_APPLICATION_ERROR`.
|
||||
- Ar trebui adaugata o **ramura noua, in afara/inainte de bucla `cursor_articol`**, activa doar cand
|
||||
`V_ARE_POLITICA = FALSE`, care sa:
|
||||
- calculeze `V_SCD`/`V_ASCD` **reutilizand `CASE`-ul existent pe tip document** (`:7398-7423` —
|
||||
acesta nu depinde de `cursor_articol`, poate fi extras/reutilizat identic);
|
||||
- calculeze `V_SCC` din sursa noua (corespondente pentru articol gestionabil / `NOM_ARTICOLE.CONT` sau
|
||||
`704` pentru negestionabil) — **doar acest pas e cel descris de decizia 27**;
|
||||
- decida un `V_ASCC` (azi vine din `NVL(crs_rand_articol.ascc, GetAnaliticByGrupUtilizatori(...))` —
|
||||
fara rand din cursor, ar trebui sa cada direct pe `GetAnaliticByGrupUtilizatori(...)`, posibil fara
|
||||
modificare, pentru ca aceasta functie nu depinde de cursor);
|
||||
- decida `V_EXPLICATIE`, `ID_VENCHELT`, `ID_SECTIE`, `CU_TVA`, `IN_VALUTA` — vezi sectiunea 7, aici e
|
||||
gaura reala, nu doar cod de rescris.
|
||||
- apeleze explicit `pack_facturare.scrie_nota(...)` si, conditionat, `descarca_gestiune(...)`, cu
|
||||
parametrii calculati mai sus, **duplicand** (nu reutilizand) logica din interiorul buclei existente.
|
||||
|
||||
Asta confirma exact avertismentul din decizia 27 insasi ("modificare in COMUN, cu impact peste toata
|
||||
suita") — nu e un `IF` adaugat pe o linie, e o ramura noua de ~40-60 linii care trebuie sa reproduca o
|
||||
parte din logica ramurii existente, cu surse diferite pentru fiecare camp.
|
||||
|
||||
## 7. Ce se pierde fata de lantul complet `CRM_POLITICI_PRET_ART -> ... -> NOTE_CONTABILE`
|
||||
|
||||
Aceasta e partea centrala a raspunsului: **fallback-ul pe cont de venit (corespondente + nomenclator +
|
||||
704) acopera doar `SCC`, nu si restul campurilor pe care lantul de politica de pret le aduce azi din
|
||||
`NOTE_CONTABILE` fara efort.** Campurile aduse azi de `D.EXPLICATIE, D.SCD, D.ASCD, D.SCC, D.ASCC,
|
||||
D.CU_TVA, D.IN_VALUTA` (`cursor_articol`, `:7248-7253, 7259-7260`) si ce se intampla cu fiecare fara
|
||||
politica:
|
||||
|
||||
- **`SCD`/`ASCD` (contul debitor si analiticul lui)**: **nu se pierde** — `V_SCD` se calculeaza deja
|
||||
independent de `NOTE_CONTABILE`, dintr-un `CASE` pe tipul de document (`:7398-7423`, ex. `461` pt aviz
|
||||
catre debitori, `418` pt aviz simplu, `crs_rand_articol.scd` doar pt factura normala — dar acesta din
|
||||
urma **tot vine din `NOTE_CONTABILE.SCD` prin cursor**, deci pt factura normala SCD s-ar pierde la fel
|
||||
ca SCC). **Corectie necesara fata de formularea initiala a intrebarii**: nu doar SCC vine din
|
||||
`NOTE_CONTABILE` pt factura normala — si SCD (contul de creante, tipic 411) vine de acolo. Decizia 27
|
||||
vorbeste doar de "cont de venit", nu de contul debitor — inseamna ca **si sursa lui `V_SCD` pentru
|
||||
factura normala ar trebui rezolvata** (probabil un cont fix, gen 411/`4111`, dar nu e in scopul
|
||||
deciziei 27 asa cum e formulata azi).
|
||||
- **`CU_TVA`**: **se pierde, fara alta sursa in cod.** Controleaza daca pretul de pe linie e interpretat
|
||||
cu sau fara TVA la scrierea sumei (`pack_facturare.scrie_nota(...)`, parametru `crs_rand_articol.cu_tva`
|
||||
la `:7463`). Nu exista nicio coloana echivalenta in `CORESP_CONT_VENCHELT` sau `NOM_ARTICOLE`. Fara ea,
|
||||
fallback-ul trebuie sa aleaga o valoare hardcodata sau sa o deriveze din alt camp deja disponibil pe
|
||||
`detalii_articol` (ex. `pret_cu_tva`, vazut ca parametru separat la `:7447` — posibil suficient, dar
|
||||
necesita verificare separata, nu facuta aici).
|
||||
- **`IN_VALUTA`**: **se pierde, fara alta sursa.** Controleaza daca linia e tratata ca fiind in valuta la
|
||||
nivel de nota contabila (`V_IN_VALUTA := crs_rand_articol.in_valuta;` la `:7470`, folosit apoi la
|
||||
discount `:7507`). La fel ca `CU_TVA`, nicio coloana echivalenta in corespondente/nomenclator.
|
||||
- **`EXPLICATIE`**: se pierde ca sablon configurat, dar are deja fallback partial in cod — `V_EXPLICATIE`
|
||||
e calculat cu un `CASE` pe tipul de operatie (`:7430-7439`, ex. concateneaza `pack_facturare.cdescriere`
|
||||
pt facturi tip 7) folosind `crs_rand_articol.explicatie` ca baza; fara cursor, baza ar fi goala/NULL,
|
||||
dar structura `CASE` insasi nu depinde de cursor — impact moderat, nu blocant.
|
||||
- **`ID_VENCHELT`**: are deja un fallback la nivel de sesiune —
|
||||
`NVL(pack_facturare.nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT))` (`:7244-7245`) — daca
|
||||
`pack_facturare.nid_venchelt` e setat global (parametru de sesiune), fallback-ul functioneaza deja fara
|
||||
politica; daca nu e setat, ramane `NULL` (fara eroare in cod, dar posibil relevant pentru rapoarte care
|
||||
folosesc aceasta dimensiune, neverificat).
|
||||
- **`ID_SECTIE`**: acelasi tipar — `NVL(pack_facturare.nid_sectie_stoc, C.ID_SECTIE)` (`:7246`), cu
|
||||
fallback pe variabila de sesiune.
|
||||
- **`ID_SET`** (identificatorul setului de nota folosit la scriere): **nu se pierde** — parametrul trimis
|
||||
efectiv la `scrie_nota` e `pack_facturare.nid_set` (variabila de sesiune, `:7462`), nu
|
||||
`crs_rand_articol.id_set` din cursor (acel camp e citit la `:7247` dar nu e folosit la apelul de
|
||||
scriere in ramura articol simplu — verificat direct in cod). Deci acest camp special nu depinde de
|
||||
politica de pret.
|
||||
|
||||
**Rezumat sectiunea 7**: din cele 7 campuri aduse de lantul politicii de pret, 2 (`ID_VENCHELT`,
|
||||
`ID_SECTIE`) au deja fallback pe variabile de sesiune si ar functiona fara politica; `EXPLICATIE` are
|
||||
impact moderat (structura de calcul ramane, doar baza se pierde); `ID_SET` nu depinde deloc de politica
|
||||
in ramura articol simplu; dar **`CU_TVA` si `IN_VALUTA` nu au nicio sursa alternativa** — sunt gaura reala
|
||||
pe care simpla adaugare a unui cont de venit din corespondente/nomenclator nu o umple. **Confirmarea
|
||||
finala a intrebarii din cerere**: da, fallback-ul pe cont de venit e insuficient pentru o nota contabila
|
||||
completa — nu pentru ca "SCC" ar fi gresit, ci pentru ca doua campuri de control (TVA, valuta) raman fara
|
||||
sursa.
|
||||
|
||||
## 8. Varianta VFP, fara sa se atinga `pack_facturare` (plan A, cerut de Marius)
|
||||
|
||||
Ideea de verificat: daca `contabilizeaza_articol` deriva `SCC` din `id_pol`, VFP ar putea sa aleaga
|
||||
singur, inainte de a trimite articolul, un `id_pol` a carui nota are deja `SCC` = contul de venit
|
||||
calculat dupa regula lui Marius — pachetul ar rula neschimbat. Raspunsul, punct cu punct:
|
||||
|
||||
### a) `id_pol` vine din VFP? Da — e trimis explicit, pe fiecare linie
|
||||
|
||||
Semnatura efectiva a procedurii apelate pe fluxul principal (verificat direct in export, nu presupus):
|
||||
```
|
||||
D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:4989-5015
|
||||
PROCEDURE adauga_articol_factura(V_ID_TEMP IN NUMBER,
|
||||
V_ID_ARTICOL IN NUMBER,
|
||||
V_SERIE IN VARCHAR2,
|
||||
V_EXPLICATIE IN VARCHAR2,
|
||||
V_ID_POL IN NUMBER,
|
||||
V_ID_GESTIUNE IN NUMBER,
|
||||
...
|
||||
V_CONT IN VARCHAR2,
|
||||
...
|
||||
```
|
||||
`V_ID_POL` e parametru de intrare explicit — Oracle nu il deriva din client/contract/tip document, il
|
||||
primeste ca atare de la VFP. **Niciun alt parametru din aceasta lista nu ofera un canal pentru
|
||||
SCC/id_set** (raspuns si la punctul d — vezi mai jos).
|
||||
|
||||
**De unde il ia azi VFP**: din controlul de cautare obligatoriu al formularului `frm_articol_factura`
|
||||
(clasa `ofacturare.vc2`):
|
||||
```
|
||||
COMUN\clase\ofacturare.vc2:6822-6825
|
||||
ADD OBJECT 'Ct_clb_politici_preturi' AS ct_clb_cautare WITH ;
|
||||
cconditie = ISNULL(poDate.id_pol) or EMPTY(poDate.id_pol), ;
|
||||
cprocedura = thisform.do_cauta_politica, ;
|
||||
cvar_afisata = poDate.nume_politica, ...
|
||||
```
|
||||
```
|
||||
COMUN\clase\ofacturare.vc2:7167-7182
|
||||
PROCEDURE do_cauta_politica
|
||||
Local loCauta
|
||||
loCauta = caut_politici_curente_util()
|
||||
If !Isnull(loCauta) And !Empty(Nvl(loCauta.id_pol,0))
|
||||
poDate.id_pol = loCauta.id_pol
|
||||
poDate.nume_politica = loCauta.nume
|
||||
...
|
||||
```
|
||||
`cconditie = ISNULL(poDate.id_pol) or EMPTY(poDate.id_pol)` e exact starea "articol fara politica" din
|
||||
cererea #13 — controlul UI cere explicit utilizatorului sa caute o politica cand campul e gol. VFP
|
||||
controleaza integral valoarea: azi vine din alegerea utilizatorului, dar nimic tehnic nu impiedica sa fie
|
||||
setata programatic, fara interactiune, la un `id_pol` calculat.
|
||||
|
||||
### b) Drumul invers e interogabil? Da — e simetricul exact al cursorului deja documentat la sectiunea 6
|
||||
|
||||
Cursorul din `contabilizeaza_articol` (`ff_...:7261-7267`) merge
|
||||
`CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI -> NOTE_CONTABILE` prin
|
||||
`A.ID_POL = B.ID_POL`, `B.ID_NOTA = C.ID_NOTA`, `C.ID_SET = D.ID_SET`. Interogarea inversa, plecand de la
|
||||
un `SCC` dorit catre `id_pol`-urile candidate, foloseste aceleasi coloane de legatura, doar fara filtrul
|
||||
pe articol:
|
||||
```sql
|
||||
SELECT DISTINCT PP.ID_POL
|
||||
FROM CRM_POLITICI_PRETURI PP
|
||||
JOIN CRM_NOTE_VANZARI NV ON NV.ID_NOTA = PP.ID_NOTA
|
||||
JOIN NOTE_CONTABILE NC ON NC.ID_SET = NV.ID_SET
|
||||
WHERE NC.SCC = :cont_venit_calculat; -- '707'/'711'/'702'/'703'/'7015'/'7018' (gestionabil) sau valoarea din NOM_ARTICOLE.CONT/'704' (negestionabil)
|
||||
```
|
||||
E o interogare noua (nu am gasit-o deja scrisa nicaieri in cod), dar foloseste exclusiv coloane si
|
||||
relatii deja confirmate ca existente si corecte (sectiunea 1 din `cont_venit_corespondente.md` + citirea
|
||||
directa a `cursor_articol` la sectiunea 6 a acestui raport). **Neverificat pe date reale**: cate randuri
|
||||
returneaza pentru fiecare `SCC` candidat — zero (nicio politica configurata cu acel SCC azi), unul
|
||||
(cazul ideal) sau mai multe (ambiguitate: care `id_pol` se alege?). Interogarea de rulat pe baza vie e
|
||||
chiar cea de mai sus, cu `:cont_venit_calculat` inlocuit pe rand cu `707`, `711`, `702`, `703`, `7015`,
|
||||
`7018`, `704`.
|
||||
|
||||
### c) Se poate insera din VFP un rand in `CRM_POLITICI_PRET_ART`? Da — cod existent, functional, deja rulat in productie
|
||||
|
||||
Doua mecanisme distincte, ambele reale:
|
||||
|
||||
**c1. RPC direct, apelabil din VFP azi** — `pack_preturi.adauga_politica_pret_art`, apelat din
|
||||
`ofacturare.vc2` in procedura de modificare a listei de preturi la NIR (`ID_SET = 231`):
|
||||
```
|
||||
COMUN\clase\ofacturare.vc2:15551-15587
|
||||
*!* verific daca exista articolul in politica de preturi
|
||||
lcSql = [Select id_pol_art, id_venchelt, proc_tvav from crm_politici_pret_art where id_pol = ] + Alltrim(Str(tnIdPol)) + [ and id_articol = ] + Alltrim(Str(tnIdArticol))
|
||||
lnSucces = goExecutor.oExecute(m.lcSql, "cPoliticiPretArt")
|
||||
...
|
||||
If Nvl(loPoliticaPretArt.id_pol_art,0) = 0
|
||||
lcSql = [begin pack_preturi.adauga_politica_pret_art(] + ;
|
||||
ALLTRIM(Str(tnIdPol)) + [,] + ; && id_pol
|
||||
Alltrim(Str(tnIdArticol)) + [,] + ; && id_articol
|
||||
Alltrim(Str(tnPretLista,20,4)) + [,] + ; && pret
|
||||
Alltrim(Str(tnPretftvaLista,20,4)) + [,] + ; && pretftva
|
||||
Alltrim(Str(tnPretvtvaLista ,20,4)) + [,] + ; && pretctva
|
||||
[NULL, ] + ; && id_valuta
|
||||
Alltrim(Str(tnProcTvav,20,4)) + [,] + ; && proc_tvav
|
||||
[NULL, ] + ; && procent
|
||||
[NULL, ] + ; && id_venchelt
|
||||
Alltrim(Str(gnIdUtil)) + ; && id_util
|
||||
[); end;]
|
||||
```
|
||||
Verifica intai daca exista deja un rand (`id_pol`, `id_articol`), si daca nu, insereaza unul nou prin RPC
|
||||
existent — **exact tiparul necesar pentru punctul (c)**: "adauga articolul X in politica de pret Y",
|
||||
apelabil din VFP fara nicio modificare de pachet (`pack_preturi` nu e `pack_facturare`).
|
||||
|
||||
**c2. Auto-populare in masa, deja existenta in `pack_facturare` insusi, dar fara parametru nou** — o
|
||||
procedura care nu ar necesita nicio modificare de cod pentru ca deja exista si e apelabila ca atare:
|
||||
```
|
||||
D:\ROA\ROAFACTURARE\docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2093-2120
|
||||
PROCEDURE completare_politica_stoc IS
|
||||
BEGIN
|
||||
IF pack_facturare.nid_politica_stoc IS NOT NULL THEN
|
||||
MERGE INTO CRM_POLITICI_PRET_ART A
|
||||
USING (SELECT ID_ARTICOL FROM NOM_ARTICOLE
|
||||
WHERE STERS = 0 AND INACTIV = 0 AND IN_STOC = 1) B
|
||||
ON (A.ID_POL = pack_facturare.nid_politica_stoc AND A.ID_ARTICOL = B.ID_ARTICOL)
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (ID_POL, ID_ARTICOL, ID_VALUTA)
|
||||
VALUES (pack_facturare.nid_politica_stoc, B.ID_ARTICOL, pack_facturare.nid_moneda_nationala);
|
||||
...
|
||||
END IF;
|
||||
END completare_politica_stoc;
|
||||
```
|
||||
Aceasta e o functie **existenta, apelabila fara nicio modificare**, care garanteaza deja un rand in
|
||||
`CRM_POLITICI_PRET_ART` pentru *orice* articol cu `IN_STOC = 1` sub o singura politica "de stoc"
|
||||
(`pack_facturare.nid_politica_stoc`). Acest id_pol e la randul lui o **optiune de configurare controlata
|
||||
din VFP**:
|
||||
```
|
||||
COMUN\clase\ofacturare.vc2:21733-21734 (Init-ul ecranului de optiuni facturare)
|
||||
actualizeaza_politica_pret(23,@gnId_pol_pret_tr,@lcPolPretTr)
|
||||
actualizeaza_politica_pret(1,@gnId_pol_pret_stoc,@lcPolPretStoc)
|
||||
...
|
||||
COMUN\clase\ofacturare.vc2:21753
|
||||
This.nidpolpretstoc = Nvl(gnId_pol_pret_stoc,0)
|
||||
```
|
||||
`gnId_pol_pret_stoc` e o optiune globala (`ID_POL_PRET_STOC`, salvata/citita prin
|
||||
`actualizeaza_politica_pret`), care alimenteaza `pack_facturare.nid_politica_stoc` la initializarea
|
||||
sesiunii Oracle. **Avertisment**: aceasta politica pare deja folosita azi pentru un scop specific
|
||||
(vanzare/evaluare la pret de stoc — `nin_valuta`, `nTipVanzareRetail` sunt tratate diferentiat pentru ea
|
||||
la `ff_...:7223-7260`), cu o singura nota/SCC configurata pentru toate articolele `IN_STOC=1`. Nu e
|
||||
direct reutilizabila pentru reteta pe 6 conturi diferite (707/711/702/703/7015/7018) ceruta de decizia
|
||||
27 — dar **arata modelul exact** ("o politica tehnica, configurata o singura data, populata automat"),
|
||||
model care se poate replica de cate ori e nevoie (o politica tehnica per `SCC` distinct), fara sa se
|
||||
toate cod in `pack_facturare`.
|
||||
|
||||
### d) Alte cai de bypass la nivel de parametru — cautate explicit, nu gasite
|
||||
|
||||
Semnatura completa a `adauga_articol_factura` (sectiunea a) si a variantelor `_deviz`/`_stoc` (deja
|
||||
citate in `cont_venit_articol_fara_politica.md`) **nu au niciun parametru pentru cont de venit sau
|
||||
`id_set` direct** — singurul levier disponibil la nivelul acestui apel e `V_ID_POL`. Nu exista un
|
||||
parametru `V_SCC`/`V_ID_SET`/`V_COD_VENIT` pe nicio varianta citita. Concluzie: **`id_pol` e singura
|
||||
cale de influenta din VFP asupra contului de venit**, exact premisa din care a pornit intrebarea.
|
||||
|
||||
### e) Verdict onest
|
||||
|
||||
**Cale VFP posibila, dar nu "zero cod" si nu fara pasi de configurare o singura data**:
|
||||
|
||||
1. VFP calculeaza `SCC`-ul dorit exact ca in decizia 27 (interogare simpla, deja tipul de cod scris in
|
||||
`update_corespondente_cvc` — sectiunea 1 a acestui raport): `CORESP_CONT_VENCHELT.CONT_VENIT` pe
|
||||
contul de gestiune pentru articol gestionabil, `NOM_ARTICOLE.CONT` (daca e 6xx/7xx) sau `'704'` pentru
|
||||
negestionabil.
|
||||
2. VFP cauta (interogarea de la punctul b) o `id_pol` existenta a carei nota are deja acel `SCC`. **Daca
|
||||
gaseste una** (plauzibil pentru conturile comune 707/711/702/703, care probabil se folosesc deja in
|
||||
politici de pret reale ale firmei) — trece la pasul 3 direct, fara nicio configurare noua.
|
||||
3. VFP se asigura (RPC `pack_preturi.adauga_politica_pret_art`, punctul c1, deja existent) ca exista un
|
||||
rand `CRM_POLITICI_PRET_ART` pentru (acel `id_pol`, articolul curent) — insereaza unul cu pretul
|
||||
tastat manual de utilizator (`tnPretLista` = pretul liniei) daca nu exista.
|
||||
4. VFP trimite acel `id_pol` in `adauga_articol_factura`, exact ca pe fluxul actual (cu politica).
|
||||
`contabilizeaza_articol` **ruleaza neschimbat** — si, bonus fata de varianta plan B (sectiunea 7),
|
||||
**campurile `CU_TVA`/`IN_VALUTA`/`EXPLICATIE`/`ASCD`/`ASCC` vin corect din nota reala configurata**,
|
||||
nu raman goale ca in fallback-ul partial din pachet.
|
||||
|
||||
**Ce nu e gratuit**:
|
||||
- **Pasul 2 poate rata** (nicio politica existenta cu acel `SCC`) — pentru un `SCC` fara precedent (de
|
||||
ex. `704`, pentru care nu am gasit nicio nota configurata azi, sectiunea 5), ar trebui creat manual,
|
||||
o singura data, un lant nou `CRM_POLITICI_PRETURI` + `CRM_NOTE_VANZARI` + `NOTE_CONTABILE` (contabilul
|
||||
configureaza nota din `frm_config_note_contabile[2007]`, deja documentat in
|
||||
`cont_venit_corespondente.md` sectiunea 1) — asta e configurare (ecran existent), nu cod nou, dar e un
|
||||
pas manual, nu automat.
|
||||
- **Efect lateral real**: o "politica tehnica" (creata special pentru acest mecanism) ar aparea in
|
||||
ecranul de cautare politici al utilizatorului (`caut_politici_curente_util()`, aceeasi functie folosita
|
||||
la cautarea normala) — trebuie fie exclusa explicit din acel cursor de cautare (filtru pe un flag/prefix
|
||||
dedicat), fie acceptata ca vizibila, cu riscul ca un utilizator sa o aleaga din greseala pentru o
|
||||
factura normala. Nu am verificat daca `caut_politici_curente_util()` are deja un filtru care ar exclude
|
||||
automat politici fara preturi reale/marcate altfel.
|
||||
- **Ambiguitatea de la pasul 2** (mai multe `id_pol` cu acelasi `SCC`) ar cere o regula de departajare
|
||||
(cea mai recenta? prima gasita? o marcare explicita "politica implicita pentru SCC X"?) — neverificat
|
||||
pe date reale, vezi sectiunea "Ramas de verificat".
|
||||
- Aceasta varianta **schimba `id_pol`-ul scris pe linie** (azi `NULL`/gol pentru articol fara politica,
|
||||
ar deveni id-ul politicii tehnice) — orice raport/ecran care azi presupune "`id_pol` gol = articol fara
|
||||
politica" (de ex. eventuale filtre in rapoarte de vanzari) ar vedea un `id_pol` populat; nu am gasit
|
||||
cod care sa faca aceasta presupunere explicit, dar nici n-am cautat-o exhaustiv (in afara bugetului).
|
||||
|
||||
**Concluzie e)**: nu e nevoie de "niciuna dintre cai" pentru un "nu" — exista o cale VFP verificabila si
|
||||
construita din piese deja functionale in productie (RPC de inserare in politica de pret, plus o
|
||||
interogare de cautare inversa noua dar directa). E **fezabila**, probabil **mai completa** decat plan B
|
||||
(rezolva si `CU_TVA`/`IN_VALUTA`, nu doar `SCC`), dar cere: (i) o interogare noua de cautare `id_pol` pe
|
||||
`SCC`, (ii) reutilizarea unui RPC existent pentru inserarea articolului in politica, (iii) potential
|
||||
configurare manuala o singura data (nota noua) pentru `SCC`-urile fara precedent, (iv) o decizie explicita
|
||||
despre cum se exclude/trateaza politica tehnica in ecranele de cautare vizibile utilizatorului.
|
||||
|
||||
## Ramas de verificat pe baza de date vie
|
||||
|
||||
- **Structura completa DDL a `CORESP_CONT_VENCHELT`** (`DESC coresp_cont_venchelt` sau echivalent):
|
||||
confirmarea coloanelor `ID_CCV` (probabil PK surogat) si `DATAORAS` (probabil timestamp tehnic),
|
||||
tipurile exacte, nullability, si daca exista o constrangere `UNIQUE`/index pe `CONT` (relevant pentru
|
||||
ambiguitatea discutata in sectiunea 2 si 4) — DDL-ul nu e in arhiva `SCRIPTURI_CLAR` (tabel pre-2009).
|
||||
Interogare de verificat: `SELECT column_name, data_type, nullable FROM user_tab_columns WHERE
|
||||
table_name = 'CORESP_CONT_VENCHELT' ORDER BY column_id;` si
|
||||
`SELECT index_name, uniqueness FROM user_indexes WHERE table_name = 'CORESP_CONT_VENCHELT';`.
|
||||
- **Continutul complet al `CONT_VENIT`** — migrarea din 2023 acopera 20 de conturi de gestiune; ramane de
|
||||
verificat daca acopera **toate** conturile de gestiune folosite azi in `NOM_ARTICOLE.CONT`/`STOC.CONT`
|
||||
in productie (altfel fallback-ul ar produce `CONT_VENIT = NULL` pentru conturi de gestiune
|
||||
neacoperite). Interogare: `SELECT DISTINCT cont FROM (SELECT cont FROM nom_articole UNION SELECT cont
|
||||
FROM stoc) WHERE cont NOT IN (SELECT cont FROM coresp_cont_venchelt WHERE sters = 0);`.
|
||||
- **Daca exista mai mult de un rand activ (`STERS = 0`) pentru acelasi `CONT`** — cod critic pentru
|
||||
join-uri fara dezambiguizare. Interogare: `SELECT cont, COUNT(*) FROM coresp_cont_venchelt WHERE
|
||||
sters = 0 GROUP BY cont HAVING COUNT(*) > 1;`.
|
||||
- **Valorile reale din `NOM_ARTICOLE.CONT` pentru articole negestionabile** — cate au deja un cont
|
||||
6xx/7xx explicit vs. cate au un cont 3xx "gresit" (articol marcat negestionabil dar cu cont de gestiune
|
||||
ramas din import) vs. cate au `NULL`/gol, ceea ce ar decide cat de des s-ar folosi de fapt fallback-ul
|
||||
pe `704`. Interogare: `SELECT SUBSTR(cont,1,1) AS clasa, COUNT(*) FROM nom_articole WHERE gestionabil =
|
||||
0 GROUP BY SUBSTR(cont,1,1);` (numele exact al coloanei `gestionabil` de verificat pe schema).
|
||||
Verificat si numele exact al coloanei `NOM_ARTICOLE.CONT` — vezi si `cont_venit_articol_fara_politica.md`.
|
||||
- **Cine citeste efectiv proprietatea `cconte` din `frm_configurare_efactura`** — nu am gasit consumatorul
|
||||
in codul static cercetat (`\.cconte\b`, zero rezultate in `COMUN`); posibil legata generic la o
|
||||
configuratie pe nume de camp, tipar comun in suita, dar neconfirmat.
|
||||
- **Comportamentul `scrie_nota`/`ACT_TEMP` la `SCD`/`SCC` NULL sau la `CU_TVA`/`IN_VALUTA` NULL** — codul
|
||||
PL/SQL nu are validare (confirmat in `nota_contabila_fara_politica.md`, sectiunea 2), dar constrangerile
|
||||
reale de tabel (`ACT`/`ACT_TEMP`, `NOT NULL`?) nu au fost verificate — ar decide daca o implementare pe
|
||||
jumatate facuta a fallback-ului ar da eroare Oracle explicita (`ORA-01400`) sau ar trece silentios.
|
||||
217
docs/cercetare/custodie_48_49_stergere_reemitere.md
Normal file
217
docs/cercetare/custodie_48_49_stergere_reemitere.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# Custodie (tipuri 48/49): stergere + reemitere intoarce curat descarcarea din custodie?
|
||||
|
||||
Cercetare read-only. Raspunde la intrebarea daca editarea prin regenerare (STERS=1 pe documentul
|
||||
vechi + document nou, in aceeasi tranzactie, mecanismul proiectat la S9 din
|
||||
`docs\plan_13_unificare_formular_facturare.md:3576-3660`) pentru facturile de marfa in custodie
|
||||
(tipurile 48/49) lasa stocul de custodie neschimbat.
|
||||
|
||||
Sursa principala citata: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(prescurtat `EXPORT` mai jos, `EXPORT:linie`). Verificat direct in aceasta runda: numerotarea din
|
||||
export **coincide** cu cea folosita in materialele anterioare citate de team-lead (`scrie_fact_aviz_custodie`
|
||||
la `:10089-10325` pentru corpul functiei si `:7521-7536` pentru locul de unde e apelata;
|
||||
`sterge_factura` la `:5432-5607`) — nu s-a gasit niciun offset.
|
||||
|
||||
**Rezultat central, care rescrie premiza cererii**: `scrie_fact_aviz_custodie` **nu se apeleaza
|
||||
niciodata pentru documente de tip 48/49**. Se apeleaza exclusiv cand `pack_facturare.ntip = 4`
|
||||
("factura din avize" — alt tip de document, complet diferit). Tipurile 48/49 sunt o ramura separata,
|
||||
care **nu atinge stocul deloc** la emitere (nu doar "il descarca prin alta cale"). Detalii mai jos.
|
||||
|
||||
## 1. Ce sunt exact tipurile 48 si 49
|
||||
|
||||
Confirmat pe cod, nu doar pe indiciul din materiale:
|
||||
|
||||
- **Denumirile** ("custodie cu descarcare K" / "custodie fara descarcare K") vin din
|
||||
`COMUN\docs\tipuri_documente_facturare.md`, deja verificate pe cod de cercetarea anterioara
|
||||
`docs\cercetare\s5_acoperire_tipuri.md` (tabelul de la liniile 102-103 de acolo).
|
||||
- **Reachable din meniu**: `Meniuri\politica.mn2`, submeniul `Marfaincus` — tip 48 la linia 29,
|
||||
tip 49 la linia 26 (citat deja corect in `s5_acoperire_tipuri.md:22,102-103,146-147`).
|
||||
- **Amandoua sunt facturi de sine statatoare** (categoria "Facturi", nu "Avize" — spre deosebire de
|
||||
42/47, care sunt avize catre custodie), sursa **VFP** de articole fiind aceeasi pentru ambele:
|
||||
`Case Inlist(tnTip, 48, 49) -> pack_facturare.cursor_articole_k(...)`
|
||||
(`COMUN\programe\ofacturare.prg:271-272` pe formularul standard, identic la `:750-752` pe prototip).
|
||||
- **`cursor_articole_k`** (`EXPORT:3595-3701`, definitia activa; exista si o declaratie in spec la
|
||||
`:382`) e o interogare de **lista de preturi**, nu o interogare de stoc/aviz: alege articolele
|
||||
dintr-o **politica de pret dedicata** (`A.ID_POL = to_number(pack_sesiune.getoptiunefirma('IDPOLPRETFACTK'))`,
|
||||
`EXPORT:3681-3682`) si **filtreaza explicit doar articole negestionabile**:
|
||||
`WHERE C.IN_STOC = 0 AND C.IN_CRM = 1 AND C.STERS = 0 AND C.INACTIV = 0` (`EXPORT:3695-3698`).
|
||||
Cu alte cuvinte: **48 si 49 sunt aceeasi sursa de articole** ("K" = politica de pret speciala
|
||||
pentru custodie, optiunea de firma `IDPOLPRETFACTK`), restransa explicit la articole
|
||||
**care nu sunt gestionate in stoc** (`IN_STOC=0` in `NOM_ARTICOLE`).
|
||||
- **Nu s-a gasit in aceasta runda ce anume distinge 48 de 49** ("cu"/"fara descarcare K") — pe tot
|
||||
codul examinat (`adauga_articol_factura`, `contabilizeaza_articol`, `cursor_articole_k`,
|
||||
`sterge_factura`), cele doua tipuri sunt tratate **identic**, fara nicio ramura care sa le separe.
|
||||
Diferenta pare sa fie doar de conventie de utilizare (business), nu de cod — semnalat la sectiunea
|
||||
"Neacoperit".
|
||||
|
||||
## 2. Ce scrie emiterea pe un document 48/49
|
||||
|
||||
**Nu se atinge stocul deloc, prin design, nu prin accident.** Lant de dovezi:
|
||||
|
||||
1. `adauga_articol_factura` (`EXPORT:4989-5220`) nu are ramura proprie pentru `ntip IN (48,49)` —
|
||||
cade in `ELSE` (`:5187-5203`), care preia `V_IN_STOC` direct din `V_IN_STOC_TEMP`, valoarea
|
||||
trimisa de VFP din grid — la randul ei populata din `cursor_articole_k.GESTIONABIL`
|
||||
(`C.IN_STOC AS GESTIONABIL`, `EXPORT:3634`), deci **intotdeauna 0** pentru articolele oferite pe
|
||||
aceste doua tipuri.
|
||||
2. `contabilizeaza_articol` (`EXPORT:7173-7547`): tip 48/49 intra in bucketul "factura normala"
|
||||
pentru determinarea `SCD`/`ASCD` (`WHEN pack_facturare.ntip <= 20 or ntip IN (... 48, 49, 51, 52)`,
|
||||
`EXPORT:7400-7412`) — scrie o nota contabila normala prin `scrie_nota` (`:7443-7467`), ca orice
|
||||
factura obisnuita, folosind conturile din politica de pret K.
|
||||
3. **Apelul catre `descarca_gestiune` e conditionat** de `detalii_articol.in_stoc = 1`
|
||||
(`EXPORT:7472-7475`, ramura `IF pack_facturare.ntip <> 4`, care e adevarata pentru 48/49). Cum
|
||||
`in_stoc` vine intotdeauna 0 pentru articolele acestor doua tipuri (pasul 1), **conditia nu se
|
||||
indeplineste niciodata** — `descarca_gestiune` nu se executa.
|
||||
4. **Confirmare independenta, in interiorul lui `descarca_gestiune` insusi**: chiar daca cineva ar
|
||||
reusi sa forteze apelul, functia are propria garda de iesire timpurie:
|
||||
```
|
||||
EXPORT:7789-7797
|
||||
-- NU SE DESCARCA GESTIUNEA PENTRU ARTICOLELE NEGESTIONABILE
|
||||
-- ASTFEL INCAT SA SE POATA FACE OPERATII GEN AVIZ DIN CUSTODIE INCLUSIV CU ARTICOLE NEGESTIONABILE
|
||||
SELECT MAX(IN_STOC) INTO lnInStoc FROM NOM_ARTICOLE WHERE ID_ARTICOL = V_ID_ARTICOL;
|
||||
if lnInStoc = 0 then GOTO SFARSIT; end if;
|
||||
```
|
||||
Comentariul insusi confirma intentia de design: articolele negestionabile (exact categoria din
|
||||
care se alimenteaza 48/49) sunt **explicit excluse** din descarcarea de gestiune, tocmai pentru
|
||||
ca fluxul de custodie sa poata folosi articole care nu au stoc urmarit.
|
||||
5. **`scrie_fact_aviz_custodie` nu se apeleaza pentru 48/49.** Singurul apel din tot pachetul
|
||||
(`EXPORT:7520-7537`) e in interiorul aceleiasi functii `contabilizeaza_articol`, pe ramura
|
||||
`ELSE` a testului `IF pack_facturare.ntip <> 4` (`:7472`) — adica **doar cand `ntip = 4`**
|
||||
("factura din avize"). Pentru 48/49, `ntip` e 48 sau 49, niciodata 4, deci acest cod **nu se
|
||||
executa niciodata** pe aceasta ramura. Functia `scrie_fact_aviz_custodie` insasi
|
||||
(`EXPORT:10089-10325`) nu scrie in `STOC`/`RUL` — cauta un rand deja existent in `RUL` (cu
|
||||
`ID_TIP_RULAJ = 0`, `EXPORT:10175`) pentru a calcula valori de cost, si scrie doar **note
|
||||
contabile** (`scrie_nota`, conturi `371/357/607/378/4428` — conturi de marfuri in custodie /
|
||||
cheltuieli, `EXPORT:10229-10324`) — e o functie de **conversie contabila**, nu de miscare de
|
||||
stoc.
|
||||
|
||||
**Concluzie sectiune**: premiza din cerere ("pe ramura custodiei se cheama `scrie_fact_aviz_custodie`
|
||||
in loc de `descarca_gestiune`") descrie corect fluxul pentru **tip 4** (factura emisa dintr-un aviz,
|
||||
posibil un aviz catre custodie 42/47 emis anterior), **nu** pentru tipurile 48/49 cerute explicit.
|
||||
Pentru 48/49, niciuna din cele doua proceduri nu scrie nimic in stoc — documentul e din start
|
||||
"fara efect de stoc", pentru ca sursa lui de articole (`cursor_articole_k`) e restransa la articole
|
||||
negestionabile.
|
||||
|
||||
## 3. Ce face stergerea (`sterge_factura`) pe un document 48/49
|
||||
|
||||
`sterge_factura` (`EXPORT:5432-5607`) nu are nicio ramura proprie pentru `V_TIP IN (48,49)`. Constantele
|
||||
speciale verificate (`EXPORT:78-86`): `nTipVanzareRetail=43`, `nTipFacturaHotel=44`,
|
||||
`nTipFacturaRestaurant=45`, `nTipNotaPlata=46`, `nTipFacturaACN=51` — 48 si 49 nu sunt printre ele.
|
||||
|
||||
`CASE`-ul principal (`EXPORT:5501-5551`) evalueaza `V_TIP` astfel pentru 48/49:
|
||||
- `V_TIP = 24`? Nu.
|
||||
- `V_TIP > 20 AND V_TIP NOT IN (44,45,46)`? **Da** — 48 si 49 cad in aceasta ramura generica
|
||||
("alte tipuri de avize", `EXPORT:5512-5523`), care face:
|
||||
```sql
|
||||
UPDATE VANZARI_CANTITATI SET STERS = 1
|
||||
WHERE ID_VANZARE_DET_AVIZ IN
|
||||
(SELECT ID_VANZARE_DET FROM VANZARI_DETALII WHERE ID_VANZARE = V_ID_VANZARE AND STERS = 0);
|
||||
```
|
||||
`VANZARI_CANTITATI` e tabelul de bookkeeping "cantitate ramasa din comanda/aviz" (Rol A, documentat
|
||||
in `docs\cercetare\s4_punct2_registru_cantitate_ramasa.md`, citat si in `s5_acoperire_tipuri.md`
|
||||
coloana "Rol"). **48/49 nu au niciodata acest rol** (`s5_acoperire_tipuri.md:103-104`: coloana Rol
|
||||
= "—" pentru ambele) — articolele lor vin din `cursor_articole_k` (lista de preturi K), nu din
|
||||
`cursor_comanda`. Deci acest `UPDATE` **actioneaza pe zero randuri** pentru documente 48/49 (nu
|
||||
exista randuri `VANZARI_CANTITATI` legate de ele) — e un no-op inofensiv, nu o eroare, dar nici o
|
||||
actiune custodie-specifica.
|
||||
- Restul ramurilor CASE (`V_TIP=4`, `V_TIP=nTipFacturaRestaurant`) nu se aplica.
|
||||
|
||||
Restul procedurii e comun tuturor tipurilor (nimic specific 48/49):
|
||||
- `UPDATE VANZARI_DETALII SET STERS=1 ... WHERE ID_VANZARE = V_ID_VANZARE` (`:5560-5564`) —
|
||||
marcheaza liniile facturii ca sterse.
|
||||
- `IF V_TIP IN (2,6,52)` pentru `CTR_RATE_FACTURI` (`:5566-5580`) — nu se aplica (48/49 nu sunt in
|
||||
lista).
|
||||
- `UPDATE VANZARI_CORESP SET STERS=1 ...` (`:5582-5585`) — sterge corespondentele
|
||||
factura<->aviz/comanda, generic.
|
||||
- CASE-ul final pe pachete externe (restaurant/hotel/ACN, `:5587-5605`) — nu se aplica pentru 48/49,
|
||||
cad in `ELSE NULL`.
|
||||
|
||||
**Nu exista, nicaieri in `sterge_factura`, vreun apel care sa reverseze explicit efectul lui
|
||||
`descarca_gestiune`** (nu s-a gasit `incarca_gestiune` sau echivalent, pentru niciun tip de document,
|
||||
nu doar 48/49 — cautare `grep -n "UPDATE STOC"` in tot pachetul, zero rezultate; singurele scrieri
|
||||
gasite in zona lui `descarca_gestiune` sunt in `RUL_TEMP`, `EXPORT:9464,10006`, tabel tranzitoriu care
|
||||
se descarca in `RUL` mai departe in flux, nu direct in acest apel). Mecanismul prin care stocul
|
||||
"revine" la stergerea unei facturi normale (`ntip<=20`) **nu a fost identificat in aceasta runda** —
|
||||
posibil printr-un filtru `WHERE VANZARI_DETALII.STERS=0` in interogarile de stoc disponibil, posibil
|
||||
prin alt pachet (`PACK_STOC`?) sau printr-un trigger, in afara `PACK_FACTURARE` si a perimetrului
|
||||
citit. Marcat explicit la "Neacoperit" — **e o intrebare generala a sistemului, nu specifica
|
||||
custodiei**, pentru ca `sterge_factura` trateaza identic (fara reversare explicita) orice tip de
|
||||
document.
|
||||
|
||||
## 4. Garzi existente la stergere — prind si cazul custodiei?
|
||||
|
||||
Cele trei garzi de la inceputul lui `sterge_factura`, verificate pe liniile citate de team-lead
|
||||
(coincid exact, fara offset):
|
||||
|
||||
- `EXPORT:5452-5462` — blocheaza daca exista **facturi de retur** (`VANZARI_CORESP.TIP=3`) pe
|
||||
documentul curent.
|
||||
- `EXPORT:5466-5476` — blocheaza daca exista **facturi/avize de retur** (`TIP IN (1,2)`) pe un
|
||||
aviz curent.
|
||||
- `EXPORT:5480-5494` — blocheaza daca exista **avize de retur** pe avizele care au generat o
|
||||
factura din aviz curenta.
|
||||
|
||||
**Niciuna nu mentioneaza custodie sau `ntip IN (48,49)`** — toate trei filtreaza exclusiv pe
|
||||
`VANZARI_CORESP.TIP IN (1,2,3)` (relatii factura<->retur / aviz<->retur), independent de tipul
|
||||
documentului curent (`V_ID_VANZARE`). Pentru un document 48/49 fara retur emis pe el, **niciuna nu
|
||||
se declanseaza** — stergerea trece liber, exact ca pentru o factura normala fara retur.
|
||||
|
||||
## 5. Verdict
|
||||
|
||||
**Regenerarea (stergere + reemitere) e SIGURA pentru documentele 48/49 in privinta stocului de
|
||||
custodie — dar dintr-un motiv diferit de cel presupus in cerere.** Nu pentru ca stergerea "intoarce
|
||||
curat" o descarcare de custodie — ci pentru ca **emiterea unui document 48/49 nu descarca nimic din
|
||||
stoc/custodie in primul rand** (sectiunea 2: sursa de articole e restransa structural la articole
|
||||
negestionabile, `IN_STOC=0`, iar `descarca_gestiune` are doua garzi independente care o opresc pentru
|
||||
astfel de articole). Stergerea (sectiunea 3) nu are nimic custodie-specific de reversat, si nici nu
|
||||
are nevoie sa aiba, pentru ca nimic custodie-specific n-a fost scris la emitere. **`ntip=4` (factura
|
||||
din avize) e ramura care foloseste efectiv `scrie_fact_aviz_custodie`, si e un tip de document
|
||||
diferit de 48/49** — premiza din cerere ("pe ramura custodiei se cheama `scrie_fact_aviz_custodie`
|
||||
in loc de `descarca_gestiune`") descrie corect tip 4, nu 48/49.
|
||||
|
||||
**Conditie sub care verdictul s-ar putea rasturna, ne-exclusa 100% in aceasta runda**: siguranta de
|
||||
mai sus depinde de invariantul "un document 48/49 contine numai articole cu `IN_STOC=0`". Daca
|
||||
articolele adaugate pe un document 48/49 ar veni vreodata **printr-o alta cale** decat
|
||||
`cursor_articole_k` (o cautare libera de articole, nerestransa la politica K), un articol gestionabil
|
||||
(`IN_STOC=1`) ar trece garda de la `adauga_articol_factura`/`contabilizeaza_articol` (sectiunea 2,
|
||||
pasul 1: `V_IN_STOC_TEMP` ar deveni 1) si **`descarca_gestiune` s-ar executa normal** — caz in care
|
||||
`sterge_factura`, care nu reverseaza explicit stocul pentru niciun tip (sectiunea 3), ar lasa un
|
||||
decalaj real. **Nu s-a gasit in aceasta runda** o cale VFP care sa permita asta pentru 48/49 (grid-ul
|
||||
de articole al acestor doua tipuri e populat o singura data, la deschiderea formularului, direct din
|
||||
`cursor_articole_k` — `COMUN\programe\ofacturare.prg:271-272`; nu exista o cautare separata de
|
||||
articole vazuta in codul citit), dar nu s-a verificat exhaustiv toate punctele de intrare posibile in
|
||||
`VANZARI_DETALII_TEMP` pentru aceste doua tipuri.
|
||||
|
||||
## Verificat direct / Dedus / Neacoperit
|
||||
|
||||
**Verificat direct pe cod (fisier:linie citat in sectiunile 1-4):**
|
||||
- Tip 48/49 = facturi (nu avize), sursa unica de articole `cursor_articole_k`, restransa la
|
||||
`IN_STOC=0`.
|
||||
- `scrie_fact_aviz_custodie` se apeleaza exclusiv pentru `ntip=4`, niciodata pentru 48/49.
|
||||
- `descarca_gestiune` are doua garzi independente (`in_stoc=1` la apelant, plus garda proprie pe
|
||||
`NOM_ARTICOLE.IN_STOC`) care o opresc pentru articolele din 48/49.
|
||||
- `sterge_factura` nu are ramura proprie pentru 48/49 (cade in bucketul generic ">20, exclus
|
||||
hotel/restaurant/notaplata"), iar actiunea acelui bucket (`VANZARI_CANTITATI`) nu are ce sa
|
||||
actioneze pentru aceste doua tipuri (fara rol in acel tabel).
|
||||
- Cele trei garzi de refuz al stergerii nu disting custodia, si nu se declanseaza pentru un document
|
||||
48/49 fara retur emis pe el.
|
||||
|
||||
**Dedus, nu verificat exhaustiv:**
|
||||
- Ca operatorul nu poate adauga pe un document 48/49 un articol gestionabil pe alta cale decat
|
||||
`cursor_articole_k` — bazat pe faptul ca grid-ul se populeaza o singura data la deschidere, dar
|
||||
n-am urmarit tot codul de interactiune al grid-ului (`adauga la lista`/editare inline) pentru cele
|
||||
doua tipuri.
|
||||
- Ca diferenta reala dintre tip 48 si tip 49 e doar de conventie/business, nu de cod — n-am gasit
|
||||
nicio ramura care sa le separe, dar nici n-am cautat in afara `PACK_FACTURARE`/`ofacturare.prg`
|
||||
(de exemplu in rapoarte sau in alte proceduri VFP care ar putea trata diferit "cu descarcare K" vs
|
||||
"fara").
|
||||
|
||||
**Neacoperit in aceasta runda:**
|
||||
- Mecanismul general prin care stocul "revine" la stergerea unei facturi **normale** (`ntip<=20`) —
|
||||
nu s-a gasit niciun `UPDATE STOC`/reversare explicita in `PACK_FACTURARE`; posibil calculat prin
|
||||
interogari care filtreaza `VANZARI_DETALII.STERS=0`, posibil in alt pachet Oracle sau prin trigger,
|
||||
in afara perimetrului citit in aceasta runda. Nu e o intrebare specifica custodiei — se aplica
|
||||
identic la orice tip de document — dar ramane deschisa.
|
||||
- Semnificatia exacta si diferenta functionala/de raportare intre "custodie cu descarcare K" (48) si
|
||||
"custodie fara descarcare K" (49) — n-am gasit in cod ce anume descarca "K" daca nu e stoc fizic
|
||||
(posibil un calcul contabil de adaos comercial specific comertului cu amanuntul, coeficientul K,
|
||||
dar nu confirmat pe cod in aceasta runda).
|
||||
- Testare pe date reale (baza de dev) a unui ciclu emitere->stergere->reemitere pe un document 48/49
|
||||
— cercetarea a fost exclusiv pe cod si `SELECT`-uri simple, fara sa ruleze fluxul.
|
||||
731
docs/cercetare/discount_document_cota_tva.md
Normal file
731
docs/cercetare/discount_document_cota_tva.md
Normal file
@@ -0,0 +1,731 @@
|
||||
# Cota de TVA a discountului de DOCUMENT (VANZARI.DISCOUNT) — program + eFactura
|
||||
|
||||
Cercetare read-only. Nu s-a modificat niciun fisier, nu s-a rulat `git_sync.ps1`, pe Oracle doar
|
||||
`SELECT`.
|
||||
|
||||
STATUS: complet.
|
||||
|
||||
Acest document e rezultatul imbinarii a doua rapoarte de cercetare pe aceeasi intrebare, facute de
|
||||
doi agenti separati. Raportul suplimentar, `discount_document_cota_tva_b.md`, ramane pe disc ca
|
||||
sursa a partii adaugate aici (sectiunile 3.1-3.3 si observatiile din 2.3 si 4.1).
|
||||
|
||||
## Raspunsul scurt
|
||||
|
||||
Discountul de document **NU se sparge pe cote**. El devine o **singura pseudo-linie negativa**
|
||||
numita `"Discount <procent> % Factura"`, careia i se atribuie **cota maxima de TVA de pe factura**
|
||||
(`Calculate Max(proc_tvav) To lnProcTvav`). eFactura chiar genereaza `cac:AllowanceCharge` la nivel
|
||||
de document, cu `TaxCategory/ID` + `Percent` + `TaxScheme` corecte si `AllowanceChargeReason =
|
||||
"Discount"`, `ReasonCode = 95` — dar **cota e cea maxima, aleasa prin `Max()`, nu de
|
||||
utilizator si nici proportionala**, iar "explicatia" e literalul hardcodat `"Discount"`.
|
||||
|
||||
Regula "cota maxima" e scrisa **de doua ori**, independent: in VFP (`Calculate Max(proc_tvav)`) si in
|
||||
PL/SQL (`PACK_FACTURARE.recalculeaza_totaluri_vanzari`, care din ea deriva `VANZARI.DISCOUNT_TVA`,
|
||||
`TOTAL_TVA` si `TOTAL_CU_TVA`). Pentru #13 asta e informatia operationala: **se schimba in doua
|
||||
locuri, nu in unul** (sectiunea 1.5).
|
||||
|
||||
Pe factura obisnuita (cote mixte, fara scutire), XML-ul rezultat e intern coerent (TaxSubtotal-urile
|
||||
includ deja pseudo-linia de discount) si **trece validarea** — verificat cu validatorul local
|
||||
DUKIntegrator, inclusiv pe cazul mixt si pe unul cu baza impozabila negativa. Deci defectul pe care
|
||||
**l-am putut dovedi** nu se vede: e o eroare **tacuta de atribuire fiscala**. Pe o factura cu 1000
|
||||
lei la 21% si 1000 lei la 11% cu discount de document 10%, se declara cu **10 lei mai putin TVA**
|
||||
decat ar trebui (sectiunea 4.4), mereu in acelasi sens — TVA colectat subdeclarat. Pe factura
|
||||
scutita XML-ul e si mai putin coerent decat atat — vezi paragraful urmator.
|
||||
|
||||
**Sectiunea 3 s-a inchis intre timp, cu un rezultat impartit.** Pe o factura obisnuita (toate
|
||||
liniile `scutit=0`, `expltva` gol), pseudo-linia de discount se contopeste corect in grupul cotei
|
||||
maxime — verificat prin masuratoare, nu doar dedus (sectiunea 3.1). Dar pe o factura **scutita,
|
||||
cu taxare inversa sau intracomunitara**, gruparea chiar se rupe: pseudo-linia formeaza un
|
||||
`TaxSubtotal` **orfan**, cu baza negativa si categorie gresita (sectiunea 3.2). Validatorul local nu
|
||||
respinge nici aceasta forma (sectiunea 3.3) — deci ramane, ca si defectul de atribuire fiscala, o
|
||||
eroare **tacuta**, nu una care blocheaza factura.
|
||||
|
||||
---
|
||||
|
||||
## 1. Cum se aplica azi discountul de document in program
|
||||
|
||||
**Cine il aplica: amandoua, fiecare pentru consumatorul lui.** Oracle stocheaza `VANZARI.DISCOUNT`
|
||||
(valoare absoluta, in moneda documentului) si `VANZARI.DISCOUNT_EVIDENTIAT`, si **isi calculeaza
|
||||
singur** TVA-ul discountului in `PACK_FACTURARE.recalculeaza_totaluri_vanzari` (sectiunea 1.5);
|
||||
prelucrarea pentru **listare si eFactura** se face separat, in cursoare VFP (sectiunile 1.1-1.4).
|
||||
Ambele folosesc aceeasi regula — cota maxima — dar o implementeaza independent.
|
||||
|
||||
### 1.1. Randul-sentinela `ZZZZ...` in `crsfactura`
|
||||
|
||||
`COMUN\programe\ofacturare_comun.prg:1886-1897` (`Procedure prelucreaza_facturacrs`):
|
||||
|
||||
```
|
||||
1886 If tnDiscount<> 0
|
||||
1888 Calculate Sum(valdiminuatftva),Sum(vvaldiminuatftva),Max(id_temp) To lnTotalBaza,lnTotalBazaVal,lnIdTemp
|
||||
1890 Append Blank
|
||||
1891 Replace denumire With Replicate('Z',20),cantitate With 1,;
|
||||
1892 pretftva With lnTotalBaza,discountftva With tnDiscount,valdiscountftva With tnDiscount,;
|
||||
1893 valdiscounttva With Round(tnDiscount * (tnProcTvav - 1),gnPc),;
|
||||
1894 vpretftva With lnTotalBazaVal,vdiscountftva With tnDiscountVal,vvaldiscountftva With tnDiscountVal,;
|
||||
1895 vvaldiscounttva With Round(tnDiscountVal * (tnProcTvav - 1),gnPVal),;
|
||||
1896 proc_Tvav With tnProcTvav,id_temp With lnIdTemp+1
|
||||
1897 Endif
|
||||
```
|
||||
|
||||
Observatii portante:
|
||||
- **baza** discountului = `Sum(valdiminuatftva)` peste **toate** liniile, indiferent de cota;
|
||||
- **TVA-ul** discountului = `tnDiscount * (tnProcTvav - 1)` — o **singura** cota, `tnProcTvav`;
|
||||
- randul e un **singur rand de document**, nu o repartizare pe linii.
|
||||
|
||||
### 1.2. De unde vine `tnProcTvav` — cota maxima de pe factura
|
||||
|
||||
Toate cele trei cai de apel calculeaza acelasi lucru:
|
||||
|
||||
| apelant | linia | cod |
|
||||
|---|---|---|
|
||||
| listare factura emisa (din lista de facturi) | `COMUN\clase\ofacturare_comun.vc2:4494-4498` (`frm_facturi.do_listeaza_formular`, 4188-4539) | `Calculate Max(proc_tvav) To lnProcTvav` -> `prelucreaza_facturacrs([crsdetalii],[crsfactura],lnProcTvav,lnDiscount,lnDiscountVal)` |
|
||||
| listare proforma | `COMUN\clase\ofacturare_comun.vc2:7293-7298` (`frm_proforme.do_listare`, 7233-7315) | idem |
|
||||
| listare din `oproceduri_facturare` | `COMUN\programe\oproceduri_facturare.prg:1386-1391` | `Select crsDetaliiListare` / `Calculate Max(proc_tvav) To lnProcTvav` |
|
||||
| facturare din stoc | `COMUN\programe\ofacturare_stoc.prg:582, 728` | `Calculate Max(proc_tvav) To lnProcTvav` |
|
||||
|
||||
**Deci cota de TVA a discountului de document = MAX(cotele de pe liniile facturii).** Nu e nici
|
||||
aleasa de utilizator, nici derivata din structura pe cote, nici proportionala. Nu exista niciun
|
||||
control in formular pentru ea si nicio coloana in `VANZARI` care sa o retina.
|
||||
|
||||
### 1.3. Randul `ZZZZ...` devine pseudo-linia `"Discount NN.NN % Factura"`
|
||||
|
||||
`COMUN\programe\ofacturare_comun.prg:1273-1392` (in `prelucreaza_factura`, scan peste
|
||||
`crsfacttemp`):
|
||||
|
||||
```
|
||||
1279 If loArticol.denumire <> Replicate('Z',20)
|
||||
... (linia normala de articol se copiaza in cursorul destinatie)
|
||||
1361 Else
|
||||
1362 loArticol.denumire = [FACTURA]
|
||||
1363 Endif
|
||||
1365 If lnPretListAviz = 2 && pret fara tva
|
||||
1367 If loArticol.discountftva <> 0
|
||||
1368 Append Blank
|
||||
1369 lnProcentDiscount = loArticol.discountftva * 100 / loArticol.pretftva
|
||||
1370 Replace denumire With [Discount ]+Alltrim(Str(lnProcentDiscount,10,2))+[ % ]+Alltrim(Proper(loArticol.denumire)),;
|
||||
1374 pretftva With (-1) * loArticol.discountftva,valftva With (-1) * loArticol.valdiscountftva,...;
|
||||
1375 ... valtva With (-1) * loArticol.valdiscounttva,;
|
||||
1376 proc_tva With loArticol.proc_Tvav,id_jtva_coloana With loArticol.id_jtva_coloana,...
|
||||
1378 Endif
|
||||
```
|
||||
|
||||
Randul `ZZZZ...` nu ajunge niciodata in cursorul final ca atare — la `:1361-1363` doar i se schimba
|
||||
denumirea in `FACTURA`, iar la `:1367-1377` se creeaza pseudo-linia
|
||||
**`"Discount 10.00 % Factura"`** cu `pretftva` si `valftva` **negative** si `proc_tva = tnProcTvav`.
|
||||
|
||||
**Coloanele atinse in cursorul final** (`crsFacturaFinala` / `crsfacturafinalaval`): `denumire`,
|
||||
`cantitate`, `pretftva` (negativ), `valftva` (negativ), `valtva` (negativ), `proc_tva`,
|
||||
`id_jtva_coloana`, `id_jtva_coloana_ex`. Deci **discountul de document mosteneste si coloana de
|
||||
jurnal TVA (`id_jtva_coloana`) a randului-sentinela** — care nu e setata la `:1891-1896`, deci
|
||||
ramane 0/blank pe randul `ZZZZ`. (Consecinta in eFactura: sectiunea 2.3.)
|
||||
|
||||
### 1.4. `discount_evidentiat` schimba cate pseudo-linii "Discount" apar
|
||||
|
||||
- `discount_evidentiat = 0` (implicit): ramura `ofacturare_comun.prg:1177-1203`. Liniile de articol
|
||||
primesc `0 As discountftva` (`:1188`), deci **nu** genereaza pseudo-linii; doar randul `ZZZZ`
|
||||
(reinserat separat prin `Where denumire = Replicate('Z',20)`, `:1193-1203`) pastreaza
|
||||
`discountftva` -> **o singura** pseudo-linie de discount, cea de document.
|
||||
- `discount_evidentiat = 1`: ramura `:1157-1173`, un singur `Insert` fara `WHERE`, `discountftva`
|
||||
pastrat per articol (coloana 20 din `group by 2,3,...,20,...`) -> **fiecare articol cu discount
|
||||
unitar produce propria pseudo-linie** `"Discount X % <articol>"`, plus cea de document. Aici
|
||||
cotele chiar sunt cele ale articolelor respective.
|
||||
|
||||
### 1.5. Regula "cota maxima" e implementata A DOUA OARA, in Oracle
|
||||
|
||||
Corectie la afirmatia de la inceputul sectiunii 1 ("Oracle stocheaza doar `VANZARI.DISCOUNT`"):
|
||||
**este incompleta**. `PACK_FACTURARE.recalculeaza_totaluri_vanzari` reface acelasi rationament
|
||||
independent, in PL/SQL —
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`, procedura de la
|
||||
`:16024`:
|
||||
|
||||
```
|
||||
16081 MAX(ROUND(decode(lnInValuta, 1,
|
||||
16082 ROUND(a1.curs * NVL(lnDiscountFactura,0) / a1.multiplicator, lnPreciziePretV),
|
||||
16085 NVL(lnDiscountFactura,0)) *
|
||||
16088 (a1.proc_tvav - 1), lnPreciziePretV)) as DISC_TVA_RON,
|
||||
16090 MAX(ROUND(NVL(lnDiscountFactura,0) * (a1.proc_tvav - 1), lnPreciziePretV)) as DISC_TVA_VAL,
|
||||
```
|
||||
|
||||
**Precizare pe forma exacta**: nu e `discount * MAX(proc_tvav)`, ci `MAX(discount * (proc_tvav-1))`
|
||||
— maximul se ia peste **produsele rotunjite**. Cat timp `lnDiscountFactura >= 0` cele doua coincid
|
||||
(acelasi rand castiga), deci efectul e identic cu al VFP-ului. Ar diverge doar pe un discount
|
||||
**negativ** (o majorare), unde `MAX` ar alege cota cea mai **mica** — n-am verificat daca un discount
|
||||
negativ e posibil in UI.
|
||||
|
||||
**Ce se scrie cu asta** (`:16049-16062` -> `:16214-16227`): nu doar `VANZARI.DISCOUNT_TVA`, ci si
|
||||
**totalurile salvate ale documentului**:
|
||||
|
||||
```
|
||||
16052 a.suma_tva_ron - a.disc_tva_ron as TOTAL_TVA,
|
||||
16053 a.suma_fara_tva_ron - a.disc_fara_tva_ron + a.suma_tva_ron - a.disc_tva_ron as TOTAL_CU_TVA,
|
||||
...
|
||||
16214 update vanzari set discount = lnDiscountFactura, discount_tva = lnDiscountTVA, ...
|
||||
16218 total_tva = lnTotalTVA, total_cu_tva = lnTotalCuTVA, ... where id_vanzare = V_ID_VANZARE;
|
||||
```
|
||||
|
||||
**Consecinta pentru #13**: regula de repartizare a discountului pe cote trebuie schimbata **in doua
|
||||
locuri, nu unul** — `prelucreaza_facturacrs` (VFP, pentru listare/eFactura/nota contabila) **si**
|
||||
`recalculeaza_totaluri_vanzari` (Oracle, pentru `VANZARI.DISCOUNT_TVA` / `TOTAL_TVA` /
|
||||
`TOTAL_CU_TVA`, adica pentru ce se vede in liste, rapoarte de sinteza si sold). Daca se schimba doar
|
||||
unul, `VANZARI.TOTAL_TVA` si TVA-ul din XML **vor diverge**. Nu am verificat care dintre cele doua
|
||||
valori e considerata azi "cea buna" acolo unde ambele sunt disponibile.
|
||||
|
||||
### 1.6. Ramura "pret cu TVA" (`lnPretListAviz = 1`) — nu atinge facturile
|
||||
|
||||
`ofacturare_comun.prg:1380-1391` (ramurile `Otherwise` / `tnDiscountEvidentiat=1 And
|
||||
lnPretListAviz=1`) seteaza pe pseudo-linia de discount **numai** `pretctva`/`valctva`/`valtva`,
|
||||
**nu** si `pretftva`/`valftva` — care raman 0. Am verificat daca asta poate ajunge in eFactura:
|
||||
**nu poate**. `lnPretListAviz` e initializat `= 2` (`COMUN\programe\ofacturare.prg:1112`) si e pus
|
||||
pe `1` intr-un singur loc, in ramura de **aviz de transfer** (`lcRaport = [AVIZ]`,
|
||||
`ofacturare.prg:1856-1861`, conditionat de `gnPretListAviz = 1`), iar avizele nu trec de filtrul
|
||||
`tip_doc_394 IN ('F','S','M','U','H')` din `xmlefactura.prg:276-279`. Deci nu e un defect eFactura.
|
||||
|
||||
---
|
||||
|
||||
## 2. Ce ajunge in XML-ul eFactura
|
||||
|
||||
**Generatorul viu** e `COMUN\programe\xmlefactura.prg` (`getXmlEFactura`), apelat prin
|
||||
`goExport.export2xml_efactura` (`COMUN\programe\oexport.prg:1586-1594`) din
|
||||
`COMUN\programe\ofacturare.prg:2126`, cu cursorul `crsFacturaFinala` (sau `crsfacturafinalaval` in
|
||||
valuta) — exact cursorul de listare (`ofacturare.prg:2108-2114`).
|
||||
`COMUN\clase\anaf_efactura.vc2` este UI-ul/transportul ANAF (trimitere, validare, preview), nu
|
||||
generatorul de XML.
|
||||
|
||||
### 2.1. Da — se genereaza `cac:AllowanceCharge` la nivel de document
|
||||
|
||||
Detectia e **euristica pe denumire + semn**, `xmlefactura.prg:230`:
|
||||
|
||||
```
|
||||
230 Iif('DISCOUNT' $ Upper(a.denumire) And a.pretftva < 0, 1, 0) As discount ;
|
||||
```
|
||||
|
||||
Pseudo-linia `"Discount 10.00 % Factura"` cu `pretftva < 0` (sectiunea 1.3) satisface ambele
|
||||
conditii. Randurile marcate `discount = 1` sunt excluse din liniile normale (`Scan For discount =
|
||||
0`, `:901`) si emise ca alocari de document, `xmlefactura.prg:756-795`:
|
||||
|
||||
```
|
||||
757 If mliniireducere > 0
|
||||
758 SELECT SUM(-1*valftva) as valftva, proc_tva ;
|
||||
759 FROM C_IES_FORM ;
|
||||
760 WHERE discount = 1 ;
|
||||
761 GROUP BY proc_tva ;
|
||||
762 ORDER BY proc_tva ;
|
||||
763 INTO CURSOR cDiscounturiTemp
|
||||
765 Select cDiscounturiTemp
|
||||
766 SCAN
|
||||
770 oallowancecharge = oinvoice.appendchild(oxml.createelement("cac:AllowanceCharge"))
|
||||
771 ... ChargeIndicator = "false"
|
||||
773 ... cbc:AllowanceChargeReasonCode = "95"
|
||||
775 ... cbc:AllowanceChargeReason = "Discount"
|
||||
777 odocallowancecharge = ... ("cbc:Amount") && BT-92
|
||||
778 odocallowancecharge.setattribute("currencyID", "RON")
|
||||
779 odocallowancecharge.Text = Alltrim(Str(m.lnValftva, 15, 2))
|
||||
780 otaxcategoryallowance = ... ("cac:TaxCategory")
|
||||
782 Select tip From C_TVA_FACTURA Where proc_tva = (m.lnProcTva - 1) * 100 Into Array agettipcota
|
||||
786 otaxcategoryallowance.lastchild.Text = Alltrim(agettipcota(1))
|
||||
787 ... ("cbc:Percent")
|
||||
788 otaxcategoryallowance.lastchild.Text = Alltrim(Str((m.lnProcTva - 1) * 100, 2, 0))
|
||||
789 otaxschemeallowance = ... ("cac:TaxScheme") / cbc:ID = "VAT"
|
||||
792 ENDSCAN
|
||||
```
|
||||
|
||||
Raspunsurile punctuale:
|
||||
|
||||
- **`cac:TaxCategory/cbc:ID`** (`S`, `AE`, `Z`, `E`, `K`, `O`): **nu e hardcodat** — se ia din
|
||||
`C_TVA_FACTURA.tip`, adica din `This.GetTipTaxa(mtipfactura, intracomunitar, taxare_inversa,
|
||||
scutit, proc_tva, @lcExplicatie, @lcMotiv, expltva)` (`xmlefactura.prg:298`), pe grupul cu
|
||||
**aceeasi cota** ca pseudo-linia de discount.
|
||||
- **`cbc:Percent`**: `(proc_tva - 1) * 100` al pseudo-liniei — adica **cota maxima de pe factura**
|
||||
(sectiunea 1.2). *Aici e raspunsul la intrebarea lui Marius: cota exista in XML, dar sursa ei e
|
||||
un `Max()`, nu o alegere.*
|
||||
- **`cbc:AllowanceChargeReason`**: literalul `"Discount"` (`:776`), **hardcodat**;
|
||||
**`cbc:AllowanceChargeReasonCode`**: `"95"` (`:774`), hardcodat. Textul real din program
|
||||
(`"Discount 10.00 % Factura"`) **nu** ajunge in XML — ramane doar in cursorul de listare.
|
||||
Deci "explicatia" pe care o cauta Marius nu exista: e o constanta.
|
||||
|
||||
### 2.2. Cote mixte: se sparge in mai multe `AllowanceCharge`?
|
||||
|
||||
**Mecanismul exista** (`GROUP BY proc_tva`, `:761` -> cate un `AllowanceCharge` per cota), dar
|
||||
**nu se activeaza pentru discountul de document**, pentru ca acesta e prin constructie o **singura**
|
||||
pseudo-linie cu o **singura** cota (`Max`). Gruparea foloseste efectiv doar cand
|
||||
`discount_evidentiat = 1`, unde exista mai multe pseudo-linii de discount **pe articol**, fiecare cu
|
||||
cota articolului ei.
|
||||
|
||||
Deci, pe o factura cu 21% si 11% si un discount de document: **un singur** `AllowanceCharge`, cu
|
||||
`Percent = 21`.
|
||||
|
||||
### 2.3. Riscuri identificate in acest bloc (nedovedite pe rulare)
|
||||
|
||||
- **`currencyID` hardcodat `"RON"`** la `:778`, in timp ce tot restul documentului foloseste
|
||||
`mmoneda` (`:820, 827, 873, 876, ...`, definit la `:266-267`). Pe o factura in valuta cu discount
|
||||
de document, `AllowanceCharge/cbc:Amount` iese cu `currencyID="RON"` iar
|
||||
`LegalMonetaryTotal/cbc:AllowanceTotalAmount` cu `currencyID="EUR"`. Acelasi tipar la acciza
|
||||
(`:803`). **Testat cu validatorul local** (sectiunea 4.3): DUKIntegrator **nu** respinge
|
||||
neconcordanta. Ramane o inconsecventa de cod, dar nu doar teoretica: sectiunea 4.1 arata un XML
|
||||
real de productie cu exact aceasta neconcordanta, confirmat pe SHA256 ca a plecat la ANAF si a
|
||||
fost **acceptat**.
|
||||
- **`Select ... Into Array agettipcota` fara garda pe `_Tally`** (`:782-786`): daca gruparea nu
|
||||
gaseste randul (egalitate pe numere in virgula mobila: campul `C_TVA_FACTURA.proc_tva` e stocat
|
||||
rotunjit, comparatia se face cu `(lnProcTva-1)*100` calculat la rulare), `agettipcota(1)` da
|
||||
eroare de variabila inexistenta. *Risc teoretic — nu l-am putut reproduce; il semnalez ca "de
|
||||
verificat", nu ca defect.*
|
||||
- **`Str((lnProcTva-1)*100, 2, 0)`** — latime 2. Pentru cotele romanesti (0, 5, 9, 11, 19, 21) e in
|
||||
regula; o cota de trei cifre ar da `**`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Coerenta cu `TaxTotal` / `TaxSubtotal`
|
||||
|
||||
**Da, baza impozabila din XML tine cont de discount** — si o face corect aritmetic.
|
||||
|
||||
`C_TVA_FACTURA` se construieste **peste toate randurile** din `C_IES_FORM`, **inclusiv**
|
||||
pseudo-linia de discount (nu exista `WHERE discount = 0`), `xmlefactura.prg:242-248`:
|
||||
|
||||
```
|
||||
242 Select(proc_tva - 1) * 100 As proc_tva, intracomunitar, taxare_inversa, scutit, expltva, ;
|
||||
243 Sum(valtva) As tva, ;
|
||||
244 Sum(valftva) As valoare, ... ;
|
||||
245 From C_IES_FORM ;
|
||||
246 Group By proc_tva, intracomunitar, taxare_inversa, scutit, expltva ;
|
||||
247 Into Cursor C_TVA_FACTURA
|
||||
```
|
||||
|
||||
`valftva`/`valtva` ale pseudo-liniei sunt negative -> grupul cotei maxime iese **deja net de
|
||||
discount**. `cbc:TaxableAmount` = `C_TVA_FACTURA.valoare` (`:828`), `cbc:TaxAmount` =
|
||||
`C_TVA_FACTURA.tva` (`:831`).
|
||||
|
||||
Totalurile inchid corect (`:863-898`):
|
||||
|
||||
```
|
||||
864 Sum valftva To mtotalnet For discount = 0 && numai liniile reale
|
||||
867 mtotalnetliniifactura = mtotalnet && BT-106 LineExtensionAmount
|
||||
869 mtotalnet = mtotalnet + mtotalcharges - mtotalallowances && BT-109 TaxExclusiveAmount
|
||||
870 mtotalbrut = mtotalnet + mtotaltva && BT-112 TaxInclusiveAmount
|
||||
882 ... AllowanceTotalAmount = mtotalallowances && BT-107
|
||||
```
|
||||
cu `mtotalallowances = mdiscounturi = Sum(-1*valftva) For discount = 1` (`:282-287`).
|
||||
|
||||
Verificare: `Σ TaxSubtotal/TaxableAmount` = suma peste toate randurile = `mtotalnetliniifactura −
|
||||
mdiscounturi` = `mtotalnet` = `TaxExclusiveAmount`. Deci **BR-CO-13** (`TaxExclusiveAmount =
|
||||
LineExtensionAmount − AllowanceTotalAmount + ChargeTotalAmount`) si **BR-CO-15** se respecta.
|
||||
|
||||
**Concluzie**: pe **factura obisnuita**, suma liniilor minus discount **da** `TaxableAmount`-ul
|
||||
declarat; discrepanta nu e aritmetica, ci **de atribuire pe cote** (sectiunea 4). Pe **factura
|
||||
scutita / cu taxare inversa / intracomunitara**, concluzia nu tine — vezi 3.1-3.3.
|
||||
|
||||
Rationamentul de mai sus presupune ca pseudo-linia de discount **cade in acelasi grup** cu liniile
|
||||
de la cota maxima. Asta e adevarat doar daca cele patru chei suplimentare din `GROUP BY`
|
||||
(`intracomunitar`, `taxare_inversa`, `scutit`, `expltva`, `xmlefactura.prg:246`) ies **egale** pe
|
||||
pseudo-linie si pe liniile reale. Subsectiunile care urmeaza verifica exact asta, prin masuratoare,
|
||||
nu prin deductie.
|
||||
|
||||
### 3.1. Verificat prin masuratoare: `IIF()` peste NULL nu se propaga ca `.NULL.` in VFP
|
||||
|
||||
Pseudo-linia are `id_jtva_coloana = 0` (randul-sentinela e `Append Blank` la
|
||||
`ofacturare_comun.prg:1890` si campul nu e setat la `:1891-1896`). Daca LEFT JOIN-ul de la
|
||||
`xmlefactura.prg:226-232` nu gaseste rand in `cJTVAVanzariTemp` pentru `id = 0`, `b.coloana_jv` iese
|
||||
`.NULL.`, si intrebarea e ce intoarce `Iif(Left(.Null.,2) = 'CE', 1, 0)` — daca s-ar propaga ca
|
||||
`.NULL.`, cheia de grupare a pseudo-liniei ar diferi de a liniilor reale pe **orice** factura.
|
||||
|
||||
Am rulat o sonda headless (`vfp9.exe -A -T`, `probe_null.prg`) care reproduce exact acest `LEFT
|
||||
JOIN` si `GROUP BY`, cu o pseudo-linie de discount pe `id_jtva_coloana = 0`:
|
||||
|
||||
```
|
||||
1) IIF(.NULL.="CE",1,0) -> tip=N isnull=.F.
|
||||
2) IIF(LEFT(.NULL.,2)="CE",1,0) -> tip=N isnull=.F.
|
||||
3) IIF(INLIST(.NULL.,...),1,0) -> tip=N isnull=.F.
|
||||
linie=ARTICOL A | coloana_jv nul=.F. | intracom=0 | scutit=0
|
||||
linie=ARTICOL B | coloana_jv nul=.F. | intracom=0 | scutit=0
|
||||
linie=Discount 10.00 % Factura | coloana_jv nul=.T. | intracom=0 | scutit=0
|
||||
4) grupuri in C_TVA_FACTURA = 1
|
||||
grup: cota=21 baza=1350 tva=283.50 (1500 - 150, corect)
|
||||
```
|
||||
|
||||
**`IIF()` intoarce ramura falsa, nu `.NULL.`**, chiar daca conditia e nula. Deci pe o factura
|
||||
interna obisnuita pseudo-linia primeste `intracomunitar=0, taxare_inversa=0, scutit=0` — exact ca
|
||||
liniile reale — si **se contopeste in grupul cotei maxime**. Sonda ramane pe disc in
|
||||
`%TEMP%\claude\...\scratchpad\probe_null.prg`, nu depinde de baza de date si se poate reface
|
||||
oricand.
|
||||
|
||||
### 3.2. Dar pe factura scutita / taxare inversa / intracomunitara, gruparea chiar se rupe
|
||||
|
||||
Contopirea de la 3.1 tine doar cat timp **si liniile reale** au `scutit=0` si `expltva` gol. Pe o
|
||||
factura scutita nu e asa:
|
||||
|
||||
| camp din cheia `Group By` (`xmlefactura.prg:246`) | liniile reale (scutite) | pseudo-linia de discount de document |
|
||||
|---|---|---|
|
||||
| `id_jtva_coloana` | id real (ex. 11) | **0** — `prelucreaza_facturacrs` nu-l seteaza niciodata pe randul `ZZZZ` (`ofacturare_comun.prg:1891-1896`) |
|
||||
| `coloana_jv` (din `LEFT JOIN`) | `'WRSCDD'` | `.NULL.` — `cJTVAVanzariTemp` e incarcat cu `where afisat > 0 and id_jtva_coloana > 0` (`updateserver.prg:578`), deci **nu exista rand cu id 0** |
|
||||
| `scutit` | **1** | **0** |
|
||||
| `expltva` | `'Scutit cu drept de deducere'` | **gol** |
|
||||
|
||||
Ultimul rand merita explicat, pentru ca e contraintuitiv: `completeaza_explicatie_tva`
|
||||
(`ofacturare_comun.prg:2077-2137`) chiar **include** pseudo-linia in lista de id-uri cautate — scanul
|
||||
de la `:2094` e `For proc_tva = 1 And Nvl(id_jtva_coloana,-1) <> -1`, iar pseudo-linia are
|
||||
`proc_tva = 1` (cota 0) si `id_jtva_coloana = 0`, deci trece filtrul. Dar interogarea de la `:2108`
|
||||
e `where id_jtva_coloana in (...)` pe `jtva_coloane`, unde id-ul 0 nu exista, asa ca `Replace expltva
|
||||
... For NVL(id_jtva_coloana,0) = m.lnIdJtva` (`:2129`) nu o atinge niciodata. Ramane goala.
|
||||
|
||||
Doua campuri diferite din cinci => **doua grupuri**. XML-ul iese cu:
|
||||
|
||||
```xml
|
||||
<cac:AllowanceCharge>
|
||||
... <cbc:AllowanceChargeReason>Discount</cbc:AllowanceChargeReason>
|
||||
<cbc:Amount currencyID="RON">539.82</cbc:Amount>
|
||||
<cac:TaxCategory><cbc:ID>Z</cbc:ID><cbc:Percent>0</cbc:Percent>...
|
||||
</cac:AllowanceCharge>
|
||||
<cac:TaxTotal><cbc:TaxAmount currencyID="RON">0.00</cbc:TaxAmount>
|
||||
<cac:TaxSubtotal><cbc:TaxableAmount currencyID="RON">-539.82</cbc:TaxableAmount> <!-- grupul orfan -->
|
||||
<cbc:TaxAmount currencyID="RON">0.00</cbc:TaxAmount>
|
||||
<cac:TaxCategory><cbc:ID>Z</cbc:ID><cbc:Percent>0</cbc:Percent>...
|
||||
<cac:TaxSubtotal><cbc:TaxableAmount currencyID="RON">4410.00</cbc:TaxableAmount> <!-- liniile reale, NEnete -->
|
||||
<cbc:TaxAmount currencyID="RON">0.00</cbc:TaxAmount>
|
||||
<cac:TaxCategory><cbc:ID>E</cbc:ID><cbc:Percent>0</cbc:Percent>
|
||||
<cbc:TaxExemptionReason>Scutit cu drept de deducere</cbc:TaxExemptionReason>...
|
||||
</cac:TaxTotal>
|
||||
```
|
||||
|
||||
Categoria `Z` a grupului orfan vine din `GetTipTaxa` ramura cu ramura (`xmlefactura.prg:1116-1140`):
|
||||
`intracomunitar=0` sare peste `'K'`, `taxare_inversa=0` sare peste `'AE'`, `scutit=0` sare peste
|
||||
`'E'`, si cade pe `Case m.lcTipFactura = 'F' And m.lnProcTva = 0` -> **`'Z'`** (`:1128-1129`).
|
||||
|
||||
In plus, cautarea categoriei pentru `AllowanceCharge` (`xmlefactura.prg:782-786`) devine ambigua:
|
||||
`Select tip From C_TVA_FACTURA Where proc_tva = 0 Into Array agettipcota` gaseste acum **doua**
|
||||
randuri (`E` si `Z`), iar codul ia neconditionat `agettipcota(1)` — primul in ordinea cursorului, nu
|
||||
cel corect prin vreun criteriu. Ordinea nu e garantata de VFP si nu a fost fixata experimental;
|
||||
categoria alocarii poate iesi `E` sau `Z` dupa caz — oricare din ele e gresita ca modelare, doar in
|
||||
mod diferit.
|
||||
|
||||
**Nu s-a reprodus scenariul end-to-end prin program** (ar fi cerut o factura scutita cu discount de
|
||||
document, iar in baza de dev nu exista — sectiunea 4.2). Ramane stabilit din citirea codului plus
|
||||
semantica `IIF`/NULL masurata la 3.1, nu dintr-o factura rulata efectiv prin program.
|
||||
|
||||
### 3.3. Validare offline: XML-ul cu grup orfan nu e respins, dar controlul negativ functioneaza
|
||||
|
||||
XML-urile de mai sus au fost construite pornind de la o factura reala acceptata de ANAF ca schelet
|
||||
si trecute prin validatorul local `DUKIntegrator.jar` (acelasi apel ca la 4.3):
|
||||
|
||||
| scenariu | ce contine | rezultat |
|
||||
|---|---|---|
|
||||
| `scutit_bug` | scutit + discount de document, **cu grupul orfan** (ce produce codul azi) | **ok** |
|
||||
| `scutit_corect` | scutit + discount, un singur `TaxSubtotal` net (varianta corecta) | **ok** |
|
||||
| `control_stricat` | `TaxExclusiveAmount` stricat intentionat, ca martor ca validatorul chiar valideaza | **eroare `BR-CO-13` + `BR-CO-15`** |
|
||||
|
||||
Controlul negativ conteaza: fara el, un „ok" pe `scutit_bug` n-ar dovedi nimic — ar putea insemna
|
||||
ca validatorul nu verifica deloc coerenta totalurilor. Cu el, e clar ca validatorul **chiar
|
||||
verifica**, si totusi trece grupul orfan.
|
||||
|
||||
**De ce trece**: regulile EN16931 pe categorii (`BR-S-08`, `BR-E-08`, `BR-Z-08`) cer ca baza
|
||||
declarata pe o categorie sa fie egala cu *suma liniilor din acea categorie minus alocarile din
|
||||
aceeasi categorie*. Grupul orfan respecta asta trivial (`0 - 539.82 = -539.82`), iar grupul `E` la
|
||||
fel (`4410.00 - 0`). Aritmetica inchide; **modelarea fiscala e cea gresita**, si asta niciun
|
||||
schematron nu prinde.
|
||||
|
||||
Aceeasi rezerva ca in 4.3: validatorul local e din ianuarie 2022 (`ro16931-ubl-1.0.8`); validatorul
|
||||
online curent al ANAF nu a fost apelat (interdictie explicita).
|
||||
|
||||
> **Verdict sectiunea 3**: contopirea corecta descrisa mai sus e confirmata prin masuratoare pentru
|
||||
> factura obisnuita (3.1) si infirmata pentru factura scutita / taxare inversa / intracomunitara
|
||||
> (3.2), unde discountul de document formeaza un `TaxSubtotal` orfan cu baza negativa si categorie
|
||||
> ambigua (`E` sau `Z`). Pe niciuna din cele doua forme validatorul local nu respinge XML-ul (3.3,
|
||||
> si 4.3 pentru cazul cu cote mixte) — deci ramane, ca si defectul din sectiunea 4, o eroare
|
||||
> **tacuta**, nu una care blocheaza factura.
|
||||
|
||||
---
|
||||
|
||||
## 4. Defect real sau gol de proiectare
|
||||
|
||||
**Verdict: NU s-a putut dovedi un defect de VALIDARE — nici pe factura obisnuita, nici pe cea
|
||||
scutita cu grupul orfan (sectiunea 3.2-3.3), validatorul local nu respinge XML-ul. S-a dovedit in
|
||||
schimb un defect de ATRIBUIRE FISCALA: pe facturi cu cote mixte, tot TVA-ul discountului de
|
||||
document se scade din cota maxima, si pe facturi scutite/taxare inversa/intracomunitare, o
|
||||
modelare fiscala gresita (grup orfan) care trece neobservata.** Adica exact ce banuia Marius, dar
|
||||
consecinta stabilita nu e "factura respinsa", ci "TVA colectat gresit sau baza modelata gresit,
|
||||
tacut".
|
||||
|
||||
> **Nuanta ramasa, dupa inchiderea ipotezei din sectiunea 3**: ipoteza grupului orfan **s-a
|
||||
> confirmat** pentru facturile scutite/taxare inversa/intracomunitare (3.2), dar validatorul local
|
||||
> **nu** o respinge (3.3) — deci nu devine, cum s-ar fi putut anticipa, un defect de validare
|
||||
> propriu-zis, ci ramane in aceeasi categorie cu restul sectiunii 4: o eroare de modelare care nu
|
||||
> blocheaza factura. Testele din 4.3 (cote mixte) si cele din 3.3 (factura scutita) acopera acum
|
||||
> ambele forme.
|
||||
|
||||
### 4.1. Dovada ca mecanismul chiar produce `AllowanceCharge` de document in productie
|
||||
|
||||
Am cautat in cele ~250 de XML-uri eFactura salvate pe disc (`D:\ROA\Efactura`, `D:\ROA\ROAEFACTURA`,
|
||||
`D:\ROA\ROAFACTURARE\Utile\efactura`) blocurile `cac:AllowanceCharge` care contin `cac:TaxCategory`
|
||||
(marca alocarii **de document**; cele de linie nu au `TaxCategory`, au `MultiplierFactorNumeric`).
|
||||
Exemple reale:
|
||||
|
||||
| fisier | Amount | TaxCategory | Percent |
|
||||
|---|---|---|---|
|
||||
| `D:\ROA\Efactura\2024_01\VENDING_MASTER_SRL\efactura_20240125_1526_BEST_POWER_DANCE_S_R_L_.xml` | 13.84 / 25.02 (doua) | S / S | 9 / 19 |
|
||||
| `D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml` | 539.82 | E | 0 |
|
||||
| `D:\ROA\Efactura\2024_03\VENDING_MASTER_SRL\efactura_20240301_3736_BRIANA_ALIMENT_SRL.xml` | 411.01 | S | 9 |
|
||||
| `D:\ROA\Efactura\2024_07\VENDING_MASTER_SRL\efactura_20240426_7446_CIA_TRADE_POSSIBLE_SRL.xml` | 176.15 | S | 9 |
|
||||
|
||||
Forma emisa (exemplu real, `...2885...`):
|
||||
```xml
|
||||
<cac:AllowanceCharge><cbc:ChargeIndicator>false</cbc:ChargeIndicator>
|
||||
<cbc:AllowanceChargeReasonCode>95</cbc:AllowanceChargeReasonCode>
|
||||
<cbc:AllowanceChargeReason>Discount</cbc:AllowanceChargeReason>
|
||||
<cbc:Amount currencyID="RON">539.82</cbc:Amount>
|
||||
<cac:TaxCategory><cbc:ID>E</cbc:ID><cbc:Percent>0</cbc:Percent>
|
||||
<cac:TaxScheme><cbc:ID>VAT</cbc:ID></cac:TaxScheme></cac:TaxCategory></cac:AllowanceCharge>
|
||||
```
|
||||
|
||||
**Precizare de onestitate**: niciunul dintre aceste patru nu pot sa-l atribui sigur discountului de
|
||||
**document**. Dimpotriva — la `...7446...` factura are linii pe 9% (2016.51) si pe 19% (1477.11),
|
||||
iar unica alocare cade pe **9%**; daca ar fi fost discount de document, `Max(proc_tvav)` ar fi dat
|
||||
**19%**. Deci acolo sunt discounturi **pe linie** cu `discount_evidentiat = 1` (sectiunea 1.4). La
|
||||
fel `...1526...`, unde cele doua alocari sunt exact 2.00% din baza fiecarei cote. **Nu am gasit pe
|
||||
disc un XML in care sa pot identifica pozitiv un discount de document.** Ce dovedesc aceste fisiere
|
||||
e ca **traseul cod -> `AllowanceCharge` cu `TaxCategory` chiar functioneaza in productie** si ca
|
||||
gruparea pe cote e reala cand exista mai multe pseudo-linii de discount.
|
||||
|
||||
**Al treilea indiciu, pe acelasi fisier `...2885...`, in sens invers.** Factura e integral **scutita
|
||||
cu drept de deducere**: toate cele trei linii sunt `E` / `Percent 0` cu `TaxExemptionReason =
|
||||
"Scutit cu drept de deducere"`, `DocumentCurrencyCode = EUR`, si are `AllowanceCharge` la nivel de
|
||||
document de 539.82 cu `TaxCategory ID = E`. Are **un singur `TaxSubtotal`**: `TaxableAmount =
|
||||
3870.18`, adica exact `4410.00 − 539.82` — **net de discount, fara grup orfan**.
|
||||
|
||||
Asta e semnificativ dupa sectiunea 3.2: `expltva` face parte din cheia de grupare, iar grupul unic
|
||||
de aici poarta `TaxExemptionReason` completat. Daca pseudo-linia de discount ar fi avut `expltva`
|
||||
gol si `id_jtva_coloana = 0` (cazul discountului de **document**, descris la 3.2), gruparea nu s-ar
|
||||
fi putut contopi — ar fi iesit doua grupuri, ca in `scutit_bug` (3.3). Contopirea observata aici
|
||||
dovedeste deci ca pseudo-linia purta un `id_jtva_coloana` real, adica era un discount **pe linie**
|
||||
(`discount_evidentiat = 1`), nu de document — un al treilea indiciu, independent de cele doua de mai
|
||||
sus (alocare pe cota minima la `...7446...`; doua alocari la `...1526...`), care coroboreaza aceeasi
|
||||
concluzie: cele patru XML-uri de productie gasite sunt discounturi pe linie, nu de document.
|
||||
|
||||
Fisierul arata insa **pozitiv forma corecta** pentru o factura scutita cu discount de document: cand
|
||||
pseudo-linia poarta `id_jtva_coloana` corect, grupul iese unul singur si net — exact forma
|
||||
recomandata la sectiunea 6, punctul 1. Deci recomandarea are un precedent real in productie, nu doar
|
||||
o constructie sintetica de test. **Rezerva onesta**: fisierul e din februarie 2024, iar inferenta
|
||||
presupune ca traseul de cod e neschimbat de atunci; ramane adevarat ca **nu exista niciun XML de
|
||||
productie identificat pozitiv ca discount de DOCUMENT**.
|
||||
|
||||
**Identitatea fisierului, verificata pe SHA256.**
|
||||
`D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml` (5922
|
||||
octeti) are acelasi SHA256 (`3303AFE34BC9E3E84DA9B3527C435DD9B8CB0BC7881D7D2F660A250237E23C64`) cu
|
||||
`4172939206.xml` din `...\TRIMISE\4172939206_3250292036.zip` — deci e exact ce a plecat la ANAF, si
|
||||
a fost **acceptat**. Continutul din `...\ERORI\4172675286_3249814520.zip` e un mesaj de eroare de
|
||||
446 octeti pentru o incarcare **anterioara**, cu alt index de incarcare, si listeaza exact doua
|
||||
reguli: `BR-CO-15` si `BR-CL-04` (cod de moneda invalid). Deci respingerea aceea **nu** e a
|
||||
fisierului cu `currencyID="RON"` discutat la sectiunea 2.3 — acela a trecut.
|
||||
|
||||
### 4.2. Datele din baza de dev — putine, dar arata ca situatia e posibila
|
||||
|
||||
`MARIUSM_AUTO@ROA_CENTRAL`, doar `SELECT`:
|
||||
- facturi nesterse cu `VANZARI.DISCOUNT <> 0`: **3** (id_vanzare 165 / 575 / 576;
|
||||
`discount_evidentiat` = 1 / 1 / 0). Toate trei au **o singura cota** pe linii (1.19, 1.24, 1.24).
|
||||
- facturi nesterse cu **cote mixte** pe linii: **34**.
|
||||
|
||||
Deci in dev nu exista intersectia "discount de document + cote mixte". **Asta nu dovedeste nimic** —
|
||||
volumele de dev sunt mici, iar ambele ingrediente exista separat. In productie combinatia e banala
|
||||
(orice comerciant cu alimente 9%/11% + nealimentare 21% care da un discount global).
|
||||
|
||||
### 4.3. Ce am testat efectiv cu validatorul ANAF, offline
|
||||
|
||||
Am folosit **validatorul local** `D:\ROA\COMUNROA\dist_efactura\DUKIntegrator.jar` (`-v FACT1`,
|
||||
reguli `ro16931-ubl-1.0.8`), acelasi pe care il apeleaza `xmlefactura.prg:1166-1194`. **Nu s-a
|
||||
trimis nimic catre ANAF; nu s-a apelat niciun serviciu web.** XML-urile de test sunt in
|
||||
`%TEMP%\claude\...\scratchpad\` (base/scenA/scenB/scenC_*), construite plecand de la un XML real
|
||||
valid.
|
||||
|
||||
| test | ce contine | rezultat |
|
||||
|---|---|---|
|
||||
| `base` | XML-ul real `...7446...`, nemodificat (martor) | **ok** |
|
||||
| `scenA` | cote mixte 21% + 11%, discount 200 lei atribuit **integral** cotei 21% (exact ce genereaza programul) | **ok** |
|
||||
| `scenB` | 100 lei @21% + 5000 lei scutit, discount 510 lei la 21% -> `TaxableAmount = -410.00`, `TaxAmount = -86.10` | **ok** |
|
||||
| `scenC_corect` | acelasi ca scenA, in EUR, cu `AllowanceCharge/Amount currencyID="EUR"` | **ok** |
|
||||
| `scenC_bug` | idem, dar cu `currencyID="RON"` pe alocare (ce face codul la `:778`) | **ok** |
|
||||
|
||||
**Concluzii, inclusiv cele care imi infirma ipotezele:**
|
||||
1. Atribuirea intregului discount cotei maxime **trece validarea** — deci nu e o eroare pe care ANAF
|
||||
sa o prinda. E o eroare **tacuta**.
|
||||
2. Ipoteza mea ca o `TaxableAmount` **negativa** ar fi respinsa e **infirmata** de validatorul local.
|
||||
3. Ipoteza ca neconcordanta de moneda (`currencyID="RON"` pe factura in EUR) ar fi respinsa e tot
|
||||
**infirmata** de validatorul local.
|
||||
4. **Rezerva**: validatorul instalat e din **ianuarie 2022** (`DUKIntegrator.jar`, 19.01.2022,
|
||||
reguli 1.0.8). Regulile ANAF s-au inasprit de atunci. Punctele 2 si 3 raman "neinfirmate de
|
||||
validatorul disponibil local", nu "garantat acceptate azi de ANAF".
|
||||
|
||||
### 4.4. Scenariul reproductibil al defectului REAL (fiscal)
|
||||
|
||||
**Factura**: client intern, in lei, `discount_evidentiat = 0`.
|
||||
- Linia 1: marfa cota standard, 1 buc x 1000.00 lei, **21%**
|
||||
- Linia 2: marfa cota redusa, 1 buc x 1000.00 lei, **11%**
|
||||
- Discount de document: **10%** -> `VANZARI.DISCOUNT = 200.00`
|
||||
|
||||
**Ce face programul**:
|
||||
- `Max(proc_tvav)` = 1.21 -> pseudo-linia de discount primeste **21%**
|
||||
(`oproceduri_facturare.prg:1387` / `ofacturare_comun.vc2:4495`)
|
||||
- `valdiscounttva = Round(200 * 0.21, 2) = 42.00` (`ofacturare_comun.prg:1893`)
|
||||
|
||||
**Ce iese in XML** (verificat ca valid — `scenA`):
|
||||
```
|
||||
AllowanceCharge: Amount 200.00, TaxCategory S, Percent 21
|
||||
TaxSubtotal S/21: TaxableAmount 800.00 TaxAmount 168.00
|
||||
TaxSubtotal S/11: TaxableAmount 1000.00 TaxAmount 110.00
|
||||
TaxAmount total 278.00 ; LineExtension 2000.00 ; TaxExclusive 1800.00 ; TaxInclusive 2078.00
|
||||
```
|
||||
|
||||
**Ce ar fi trebuit** (discount repartizat proportional cu baza: 100 lei pe fiecare cota):
|
||||
```
|
||||
AllowanceCharge #1: 100.00, S, 21 AllowanceCharge #2: 100.00, S, 11
|
||||
TaxSubtotal S/21: 900.00 / 189.00
|
||||
TaxSubtotal S/11: 900.00 / 99.00
|
||||
TaxAmount total 288.00 ; TaxInclusive 2088.00
|
||||
```
|
||||
|
||||
**Diferenta: 10.00 lei TVA colectat in minus**, adica 5% din TVA-ul facturii — si creste cu ecartul
|
||||
dintre cote si cu marimea discountului. **Sensul e mereu acelasi**: cota maxima absoarbe tot
|
||||
discountul, deci TVA-ul declarat e **prea mic**. Riscul e al emitentului (TVA colectat
|
||||
subdeclarat), nu al ANAF-ului care respinge documentul. Aceeasi valoare gresita ajunge si in nota
|
||||
contabila si in jurnalul de TVA, pentru ca `valdiscounttva` e acelasi camp.
|
||||
|
||||
**Caz-limita, tot valid dupa validator dar clar absurd** (`scenB`): factura cu 100 lei la 21% si
|
||||
5000 lei scutit, discount de document 10% (510 lei). Tot discountul intra pe cota 21%, unde baza e
|
||||
100 lei -> `TaxSubtotal S/21` iese cu `TaxableAmount = -410.00` si `TaxAmount = -86.10`. Factura
|
||||
declara **TVA negativ** pe o livrare normala. Nu e nevoie de cote diferite de zero pe ambele parti:
|
||||
ajunge o factura in care liniile de la cota maxima sunt o mica parte din total.
|
||||
|
||||
### 4.5. Gol de proiectare, distinct de defect
|
||||
|
||||
**Explicatia ("reason") nu exista ca notiune in program.** In XML `AllowanceChargeReason` e literalul
|
||||
`"Discount"` si `ReasonCode` e `"95"`, ambele hardcodate (`xmlefactura.prg:774-776`). Textul construit
|
||||
in VFP — `"Discount 10.00 % Factura"` (`ofacturare_comun.prg:1370`) — **nu ajunge in XML**. Nu exista
|
||||
nicio coloana pe `VANZARI` pentru motivul discountului si niciun control in formular. Deci raspunsul
|
||||
la a doua jumatate a intrebarii lui Marius: **nu, discountul de document nu are azi explicatie —
|
||||
are o constanta.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Recomandare pentru formularul unificat (#13)
|
||||
|
||||
**Sa NU ceara utilizatorului cota. Sa sparga discountul automat, proportional cu baza fiecarei cote,
|
||||
si sa expuna doar un camp optional de motiv.** Argumentul e ca discountul de document nu *are* o cota
|
||||
proprie de ales: prin definitie e un procent aplicat bazei intregii facturi (`lnTotalBaza =
|
||||
Sum(valdiminuatftva)` peste toate liniile, `ofacturare_comun.prg:1888`), deci natura lui de TVA e
|
||||
determinata de liniile pe care le reduce, nu de o optiune. A pune operatorul sa aleaga o cota inseamna
|
||||
a-i cere sa ia o decizie fiscala pe care datele o dau deja, si a deschide o a doua cale de a gresi —
|
||||
azi `Max()` greseste consecvent, un camp liber ar greseste imprevizibil. Repartizarea proportionala e
|
||||
si singura care satisface modelul EN16931, unde `TaxableAmount`-ul fiecarei categorii se calculeaza ca
|
||||
*net de linii al categoriei minus alocarile de document ale aceleiasi categorii*. **Costul de
|
||||
implementare e mic, dar atinge doua locuri, nu unul** (sectiunea 1.5): `prelucreaza_facturacrs`
|
||||
(`ofacturare_comun.prg:1886-1897`) trebuie sa insereze cate un rand-sentinela **per cota** in loc de
|
||||
unul singur, cu `tnDiscount` repartizat pe baza fiecarei cote (ultima cota preia diferenta de
|
||||
rotunjire, ca suma sa fie exact `VANZARI.DISCOUNT`), **si** `recalculeaza_totaluri_vanzari`
|
||||
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16081-16092`) trebuie sa inlocuiasca `MAX(...)` cu suma
|
||||
repartizarii, altfel `VANZARI.TOTAL_TVA` ramane pe regula veche si diverge de XML; **eFactura nu are
|
||||
nevoie de nicio modificare structurala** —
|
||||
`xmlefactura.prg:758-792` grupeaza deja `GROUP BY proc_tva` si emite cate un `AllowanceCharge` per
|
||||
cota, iar `C_TVA_FACTURA` include deja pseudo-liniile in `TaxSubtotal`. Doua consecinte de decis
|
||||
explicit cu Marius: **factura tiparita** va arata N randuri "Discount X % Factura" in loc de unul (pe
|
||||
cote mixte), si **nota contabila** primeste TVA-ul discountului spart pe cote. Pentru "explicatie",
|
||||
recomand un singur camp text optional pe document, folosit ca `AllowanceChargeReason` in locul
|
||||
constantei `"Discount"` (`ReasonCode` ramane `95`) — util si pe hartie, si e singura bucata care chiar
|
||||
cere UI nou.
|
||||
|
||||
**Precizare, dupa sectiunea 3.2**: "eFactura nu are nevoie de modificare" e adevarat doar daca
|
||||
fiecare pseudo-linie noua, per cota, primeste si `id_jtva_coloana` **al grupului pe care il reduce**,
|
||||
nu doar `proc_tva` al lui. Cota singura nu ajunge — cheia de grupare din `xmlefactura.prg:246` are
|
||||
cinci campuri, iar `expltva` se completeaza tot prin `id_jtva_coloana` (sectiunea 3.2). O
|
||||
implementare care seteaza doar `proc_tva` pe randul-sentinela ar lasa grupul orfan exact acolo unde
|
||||
e azi, pe facturile scutite / cu taxare inversa / intracomunitare. Fisierul `...2885...` de la
|
||||
sectiunea 4.1 e dovada pozitiva ca, atunci cand `id_jtva_coloana` e setat corect, forma iese unul
|
||||
singur si net — deci reparatia are deja un precedent real in productie, nu doar o constructie de
|
||||
test.
|
||||
|
||||
---
|
||||
|
||||
## Verificat direct vs. dedus
|
||||
|
||||
**Verificat direct (citit in cod, cu `fisier:linie`)**
|
||||
- randul-sentinela `ZZZZ` si campurile lui — `ofacturare_comun.prg:1886-1897`
|
||||
- transformarea in pseudo-linia `"Discount % Factura"` — `ofacturare_comun.prg:1361-1392`
|
||||
- `Max(proc_tvav)` pe toate cele patru cai de apel — `oproceduri_facturare.prg:1387`,
|
||||
`ofacturare_comun.vc2:4495` si `:7294`, `ofacturare_stoc.prg:582`
|
||||
- a doua implementare a aceleiasi reguli, in PL/SQL, si campurile pe care le scrie —
|
||||
`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16024` (procedura), `:16081-16092` (`MAX(...)`),
|
||||
`:16049-16062` (`TOTAL_TVA`/`TOTAL_CU_TVA` derivate din el), `:16214-16227` (`update vanzari`)
|
||||
- euristica de detectie in eFactura — `xmlefactura.prg:230`
|
||||
- generarea `AllowanceCharge` de document, cu `GROUP BY proc_tva` — `xmlefactura.prg:756-795`
|
||||
- sursa lui `TaxCategory/ID` (`GetTipTaxa`, `xmlefactura.prg:1103-1143`) si a lui `Percent`
|
||||
- includerea pseudo-liniei in `C_TVA_FACTURA` -> `TaxSubtotal` — `xmlefactura.prg:242-248, 822-851`
|
||||
- inchiderea totalurilor `LegalMonetaryTotal` — `xmlefactura.prg:863-898`
|
||||
- ca generatorul viu e `xmlefactura.prg`, nu `anaf_efactura` — `oexport.prg:1586-1594`,
|
||||
`ofacturare.prg:2107-2126`
|
||||
- ca ramura "pret cu TVA" (unde pseudo-linia iese cu `valftva = 0`) nu poate ajunge in eFactura —
|
||||
`ofacturare.prg:1112, 1856-1861` + filtrul `tip_doc_394` din `xmlefactura.prg:276-279`
|
||||
- semantica `LEFT JOIN` + `IIF`/`INLIST` peste `.NULL.` in cheia de grupare (sectiunea 3.1/3.2) —
|
||||
`xmlefactura.prg:226-248`
|
||||
- de ce grupul orfan nu poate fi completat prin `completeaza_explicatie_tva` — filtrul de scan vs.
|
||||
filtrul de update (`ofacturare_comun.prg:2094` vs. `:2108, 2129`, sectiunea 3.2)
|
||||
- sursa categoriei `Z` a grupului orfan, ramura cu ramura in `GetTipTaxa` — `xmlefactura.prg:1116-1140`
|
||||
(sectiunea 3.2)
|
||||
- incarcarea `cJTVAVanzariTemp` doar cu `id_jtva_coloana > 0`, motivul pentru care randul 0 nu se
|
||||
gaseste — `updateserver.prg:578` (sectiunea 3.2)
|
||||
|
||||
**Verificat prin rulare**
|
||||
- 4 XML-uri reale de productie cu `AllowanceCharge` de document (sectiunea 4.1)
|
||||
- 3 facturi cu discount in baza de dev, 34 cu cote mixte (`SELECT`, sectiunea 4.2)
|
||||
- 5 validari DUKIntegrator offline pe cote mixte (sectiunea 4.3) — **au infirmat doua dintre
|
||||
ipotezele initiale**
|
||||
- semantica `IIF()` peste `.NULL.` masurata cu o sonda headless (`probe_null.prg`, sectiunea 3.1) —
|
||||
intoarce ramura falsa, nu `.NULL.`
|
||||
- 3 validari DUKIntegrator offline pe scenariul grupului orfan (`scutit_bug`, `scutit_corect`,
|
||||
plus `control_stricat` ca martor negativ, sectiunea 3.3) — grupul orfan **nu** e respins
|
||||
- identitatea SHA256 a XML-ului EUR de productie fata de fisierul chiar trimis la ANAF, si citirea
|
||||
celor doua zip-uri (`TRIMISE`/`ERORI`) care arata ca respingerea gasita e a unei incarcari
|
||||
anterioare, pentru alte reguli (sectiunea 4.1)
|
||||
|
||||
**Dedus, NEverificat prin rulare**
|
||||
- calculul numeric din scenariul 4.4 (aritmetica pe formulele citite, nu o factura rulata efectiv
|
||||
prin program)
|
||||
- riscul `agettipcota(1)` fara garda pe `_Tally` (`xmlefactura.prg:782-786`) — n-am reusit sa-l
|
||||
provoc si nu-l afirm ca defect
|
||||
- ce se intampla in nota contabila / jurnalul de TVA cu `valdiscounttva` — am dedus din faptul ca e
|
||||
acelasi camp, n-am urmarit consumatorii
|
||||
- scenariul "factura scutita + discount de document" **end-to-end prin program** (sectiunea 3.2):
|
||||
gruparea separata e stabilita din cod + semantica `IIF`/NULL masurata, nu dintr-o factura reala
|
||||
rulata prin program — in baza de dev nu exista intersectia (sectiunea 4.2)
|
||||
- ordinea randurilor in `C_TVA_FACTURA` dupa `GROUP BY` (deci daca `agettipcota(1)` alege `E` sau
|
||||
`Z` in cazul grupului orfan) — nu e garantata de VFP si nu a fost fixata experimental
|
||||
|
||||
**Neacoperit — si stiut ca neacoperit**
|
||||
- daca `VANZARI.TOTAL_TVA` (Oracle, sectiunea 1.5) si TVA-ul din XML sunt confruntate undeva si care
|
||||
primeaza — am constatat doar ca ambele exista si folosesc aceeasi regula
|
||||
- daca un discount de document **negativ** e posibil din UI (ar inversa `MAX`-ul din Oracle, 1.5)
|
||||
- calea in **valuta** (`crsfacturafinalaval` / `prelucreaza_factura_valuta`,
|
||||
`ofacturare_comun.prg:1540+`) — am presupus simetrie cu cea in lei pe baza campurilor `v*`
|
||||
paralele de la `:1894-1895`, n-am parcurs-o linie cu linie
|
||||
- daca `Max(proc_tvav)` e cota corecta si pentru **proforme** in vreun sens special
|
||||
- comportamentul validatorului ANAF **curent** (cel local e din 2022, atat pentru cote mixte cat si
|
||||
pentru grupul orfan)
|
||||
|
||||
---
|
||||
|
||||
## Intrebari ramase pentru Marius
|
||||
|
||||
1. **Repartizarea proportionala se aplica retroactiv la relistare?** O factura veche relistata sau
|
||||
retrimisa in eFactura ar genera alt XML decat cel trimis initial. Se accepta, sau noul
|
||||
comportament se leaga de o data / de un flag?
|
||||
2. **Pe factura tiparita**: e in regula sa apara N randuri "Discount X % Factura" (unul pe cota), sau
|
||||
vrei un singur rand vizibil si spargerea doar in XML / nota contabila?
|
||||
3. **Rotunjirea**: pe cine cade diferenta de rotunjire cand discountul nu se imparte exact
|
||||
(ex. 100 lei pe trei cote)? Propunerea mea: pe cota cu baza cea mai mare.
|
||||
4. **Discountul care depaseste baza unei cote** (cazul din 4.4, factura majoritar scutita): dupa
|
||||
repartizarea proportionala problema dispare de la sine — confirmi ca nu mai e nevoie de nicio
|
||||
garda separata?
|
||||
5. **Campul de motiv**: il vrei per document (un singur text), sau e suficient sa ramana constanta
|
||||
`"Discount"` si sa nu adaugam UI?
|
||||
6. **`discount_evidentiat`**: mai e folosit in productie? Schimba semnificativ ce se tipareste
|
||||
(sectiunea 1.4) si e a doua sursa de pseudo-linii "Discount" in XML.
|
||||
7. **Cele doua implementari ale regulii** (VFP + Oracle, sectiunea 1.5): se schimba amandoua in
|
||||
aceeasi livrare, sau Oracle ramane pe regula veche o vreme? Daca raman desincronizate,
|
||||
`VANZARI.TOTAL_TVA` si TVA-ul din eFactura vor diferi pe facturile cu cote mixte si discount.
|
||||
8. **Discount de document negativ** (majorare): e posibil din UI? Daca da, `MAX`-ul din Oracle alege
|
||||
cota cea mai **mica**, iar `Max(proc_tvav)` din VFP tot pe cea mai mare — adica cele doua
|
||||
implementari ar diverge deja azi, inainte de orice modificare.
|
||||
|
||||
189
docs/cercetare/discount_document_cota_tva_b.md
Normal file
189
docs/cercetare/discount_document_cota_tva_b.md
Normal file
@@ -0,0 +1,189 @@
|
||||
# Supliment: gruparea pseudo-liniei de discount in `C_TVA_FACTURA`
|
||||
|
||||
Completare la `discount_document_cota_tva.md`, scrisa de al doilea agent pe aceeasi intrebare.
|
||||
**Nu repeta** raportul principal — acopera exact punctul pe care acela il lasa deschis la finalul
|
||||
sectiunii 3 („ipoteza gruparii separate — nu am atins-o deloc, o verifica alt agent"), plus doua
|
||||
verificari prin rulare pe care raportul principal nu le are.
|
||||
|
||||
Cercetare read-only: niciun fisier de cod atins, zero write-back, pe Oracle numai `SELECT`, niciun
|
||||
apel catre ANAF (validarea s-a facut **offline**, cu DUKIntegrator din `D:\ROA\COMUNROA\dist_efactura`).
|
||||
|
||||
## Verdict in 5 randuri
|
||||
|
||||
Ipoteza pe care o ridicasem — ca pseudo-linia de discount de document, avand `id_jtva_coloana = 0`,
|
||||
iese din `LEFT JOIN` cu `coloana_jv` NULL si formeaza propriul grup in `C_TVA_FACTURA`, cu
|
||||
`TaxableAmount` negativ — este **infirmata pentru factura obisnuita** si **confirmata pentru factura
|
||||
scutita / cu taxare inversa / intracomunitara**. In al doilea caz XML-ul chiar iese cu doua
|
||||
`TaxSubtotal`-uri pe cota 0, unul cu baza negativa si categoria `Z`. **Dar validatorul nu-l
|
||||
respinge** — l-am rulat. Deci: comportament gresit ca modelare, nu defect care blocheaza factura.
|
||||
|
||||
---
|
||||
|
||||
## 1. Ce am masurat, nu dedus: `IIF()` peste NULL in VFP
|
||||
|
||||
Toata ipoteza atarna de o singura intrebare: ce intoarce `Iif(Left(.Null.,2) = 'CE', 1, 0)`. Daca ar
|
||||
intoarce `.NULL.`, cheia de grupare a pseudo-liniei ar diferi de a liniilor reale si gruparea s-ar
|
||||
rupe pe **orice** factura.
|
||||
|
||||
Am rulat o sonda headless (`vfp9.exe -A -T`) care reproduce exact `LEFT JOIN`-ul si `GROUP BY`-ul din
|
||||
`xmlefactura.prg:226-248`, cu o pseudo-linie de discount pe `id_jtva_coloana = 0`:
|
||||
|
||||
```
|
||||
1) IIF(.NULL.="CE",1,0) -> tip=N isnull=.F.
|
||||
2) IIF(LEFT(.NULL.,2)="CE",1,0) -> tip=N isnull=.F.
|
||||
3) IIF(INLIST(.NULL.,...),1,0) -> tip=N isnull=.F.
|
||||
linie=ARTICOL A | coloana_jv nul=.F. | intracom=0 | scutit=0
|
||||
linie=ARTICOL B | coloana_jv nul=.F. | intracom=0 | scutit=0
|
||||
linie=Discount 10.00 % Factura | coloana_jv nul=.T. | intracom=0 | scutit=0
|
||||
4) grupuri in C_TVA_FACTURA = 1
|
||||
grup: cota=21 baza=1350 tva=283.50 (1500 - 150, corect)
|
||||
```
|
||||
|
||||
**`IIF()` intoarce ramura falsa, nu `.NULL.`**, chiar daca conditia e nula. Deci pe o factura
|
||||
interna obisnuita pseudo-linia primeste `intracomunitar=0, taxare_inversa=0, scutit=0` — exact ca
|
||||
liniile reale — si **se contopeste in grupul cotei maxime**. Sectiunile 3 si 4 din raportul principal
|
||||
raman valabile.
|
||||
|
||||
Sonda: `...\scratchpad\probe_null.prg`. Nu depinde de baza de date si se poate reface oricand.
|
||||
|
||||
## 2. Unde ipoteza se confirma totusi: facturile scutite / taxare inversa / intracomunitare
|
||||
|
||||
Egalitatea de mai sus tine doar cat timp **si liniile reale** au `scutit=0` si `expltva` gol. Pe o
|
||||
factura scutita nu e asa:
|
||||
|
||||
| camp din cheia `Group By` (`xmlefactura.prg:246`) | liniile reale (scutite) | pseudo-linia de discount de document |
|
||||
|---|---|---|
|
||||
| `id_jtva_coloana` | id real (ex. 11) | **0** — `prelucreaza_facturacrs` nu-l seteaza niciodata pe randul `ZZZZ` (`ofacturare_comun.prg:1891-1896`) |
|
||||
| `coloana_jv` (din `LEFT JOIN`) | `'WRSCDD'` | `.NULL.` — `cJTVAVanzariTemp` e incarcat cu `where afisat > 0 and id_jtva_coloana > 0` (`updateserver.prg:578`), deci **nu exista rand cu id 0** |
|
||||
| `scutit` | **1** | **0** |
|
||||
| `expltva` | `'Scutit cu drept de deducere'` | **gol** |
|
||||
|
||||
Ultimul rand merita explicat, pentru ca e contraintuitiv: `completeaza_explicatie_tva`
|
||||
(`ofacturare_comun.prg:2077-2137`) chiar **include** pseudo-linia in lista de id-uri cautate — scanul
|
||||
de la `:2094` e `For proc_tva = 1 And Nvl(id_jtva_coloana,-1) <> -1`, iar pseudo-linia are
|
||||
`proc_tva = 1` (cota 0) si `id_jtva_coloana = 0`, deci trece filtrul. Dar interogarea de la `:2108`
|
||||
e `where id_jtva_coloana in (...)` pe `jtva_coloane`, unde id-ul 0 nu exista, asa ca `Replace expltva
|
||||
... For NVL(id_jtva_coloana,0) = m.lnIdJtva` (`:2129`) nu o atinge niciodata. Ramane goala.
|
||||
|
||||
Doua campuri diferite din cinci => **doua grupuri**. XML-ul iese cu:
|
||||
|
||||
```xml
|
||||
<cac:AllowanceCharge>
|
||||
... <cbc:AllowanceChargeReason>Discount</cbc:AllowanceChargeReason>
|
||||
<cbc:Amount currencyID="RON">539.82</cbc:Amount>
|
||||
<cac:TaxCategory><cbc:ID>Z</cbc:ID><cbc:Percent>0</cbc:Percent>...
|
||||
</cac:AllowanceCharge>
|
||||
<cac:TaxTotal><cbc:TaxAmount currencyID="RON">0.00</cbc:TaxAmount>
|
||||
<cac:TaxSubtotal><cbc:TaxableAmount currencyID="RON">-539.82</cbc:TaxableAmount> <!-- grupul orfan -->
|
||||
<cbc:TaxAmount currencyID="RON">0.00</cbc:TaxAmount>
|
||||
<cac:TaxCategory><cbc:ID>Z</cbc:ID><cbc:Percent>0</cbc:Percent>...
|
||||
<cac:TaxSubtotal><cbc:TaxableAmount currencyID="RON">4410.00</cbc:TaxableAmount> <!-- liniile reale, NEnete -->
|
||||
<cbc:TaxAmount currencyID="RON">0.00</cbc:TaxAmount>
|
||||
<cac:TaxCategory><cbc:ID>E</cbc:ID><cbc:Percent>0</cbc:Percent>
|
||||
<cbc:TaxExemptionReason>Scutit cu drept de deducere</cbc:TaxExemptionReason>...
|
||||
</cac:TaxTotal>
|
||||
```
|
||||
|
||||
Categoria `Z` a grupului orfan vine din `GetTipTaxa` ramura cu ramura (`xmlefactura.prg:1116-1140`):
|
||||
`intracomunitar=0` sare peste `'K'`, `taxare_inversa=0` sare peste `'AE'`, `scutit=0` sare peste
|
||||
`'E'`, si cade pe `Case m.lcTipFactura = 'F' And m.lnProcTva = 0` -> **`'Z'`** (`:1128-1129`).
|
||||
|
||||
In plus, cautarea categoriei pentru `AllowanceCharge` (`xmlefactura.prg:782-786`) devine ambigua:
|
||||
`Select tip From C_TVA_FACTURA Where proc_tva = 0 Into Array agettipcota` gaseste acum **doua**
|
||||
randuri (`E` si `Z`), iar codul ia neconditionat `agettipcota(1)` — primul in ordinea cursorului, nu
|
||||
cel corect prin vreun criteriu.
|
||||
|
||||
**Nu am reprodus scenariul end-to-end prin program** (ar fi cerut o factura scutita cu discount de
|
||||
document, si in baza de dev nu exista — vezi punctul 4). Il stabilesc din citirea codului plus
|
||||
semantica `IIF`/NULL masurata la punctul 1.
|
||||
|
||||
## 3. Validare offline: XML-ul gresit **nu** e respins
|
||||
|
||||
Am construit XML-urile si le-am trecut prin DUKIntegrator
|
||||
(`java -jar DUKIntegrator.jar -c <config> -v FACT1 <in.xml> <out.txt>`, exact apelul din
|
||||
`xmlefactura.prg:1192`), pornind de la o factura reala acceptata de ANAF ca schelet:
|
||||
|
||||
| scenariu | fisier | rezultat |
|
||||
|---|---|---|
|
||||
| scutit + discount de document, **cu grupul orfan** (ce produce codul azi) | `scutit_bug.xml` | **ok** |
|
||||
| scutit + discount, un singur `TaxSubtotal` net (varianta corecta) | `scutit_corect.xml` | **ok** |
|
||||
| cote mixte 21%+11%, tot discountul pe 21% (ce produce codul azi) | `mixt_azi.xml` | **ok** |
|
||||
| cote mixte 21%+11%, discount repartizat proportional | `mixt_proportional.xml` | **ok** |
|
||||
| discount mai mare decat baza cotei maxime -> `TaxableAmount = -400.00` si `TaxAmount = -84.00` pe grupul 21% | `mixt_baza_negativa.xml` | **ok** |
|
||||
| **control negativ**: `TaxExclusiveAmount` stricat intentionat | `control_stricat.xml` | **eroare BR-CO-13 + BR-CO-15** |
|
||||
|
||||
Controlul negativ conteaza: fara el, cele cinci „ok" n-ar dovedi nimic. Validatorul chiar valideaza.
|
||||
|
||||
**De ce trec**: regulile EN16931 pe categorii (BR-S-08, BR-E-08, BR-Z-08) cer ca baza declarata pe o
|
||||
categorie sa fie egala cu *suma liniilor din acea categorie minus alocarile din aceeasi categorie*.
|
||||
Grupul orfan respecta asta trivial (`0 - 539.82 = -539.82`), iar grupul `E` la fel (`4410.00 - 0`).
|
||||
Aritmetica inchide; **modelarea fiscala e cea gresita**, si asta niciun schematron nu prinde.
|
||||
|
||||
Avertisment onest: validatorul local e din 2022 (`ro16931-ubl-1.0.8`, jar-ul din ianuarie 2022).
|
||||
Validatorul online curent al ANAF poate fi mai strict. **Nu l-am apelat** — interdictie explicita.
|
||||
|
||||
## 4. De ce nu am putut proba pe date reale
|
||||
|
||||
- In baza accesibila (`MARIUSM_AUTO@ROA_CENTRAL`) exista **3** facturi cu discount de document, toate
|
||||
cu **o singura cota** si toate anterioare eFacturii (2008, 2014 x2), niciuna cu `efactura = 1`.
|
||||
Separat exista 34 de facturi cu cote mixte, dar **niciuna** cu discount de document. Deci
|
||||
intersectia care ne intereseaza e goala.
|
||||
**Asta nu inseamna ca nu apare in productie** — baza de dev are 712 facturi in total.
|
||||
- Cele 4 XML-uri de productie cu `AllowanceCharge` de document pe care le-am examinat sunt, dupa
|
||||
toate semnele, **discounturi pe linie** cu `discount_evidentiat = 1`, nu discounturi de document:
|
||||
cel de la `2024_07\...CIA_TRADE_POSSIBLE` are cote 9% si 19%, iar alocarea unica e pe **9%** —
|
||||
adica pe cota **minima**, ceea ce regula `Max(proc_tvav)` nu poate produce; iar
|
||||
cel de la `2024_01\...BEST_POWER_DANCE` are **doua** alocari (9% si 19%), ceea ce discountul de
|
||||
document, fiind o singura pseudo-linie, nu poate produce nici el.
|
||||
Nu am gasit niciun XML de productie care sa fie sigur discount de **document**.
|
||||
- Firmele din `D:\ROA\Efactura` (VENDING_MASTER, CLEVER_MOTORS, ...) nu au scheme in baza accesibila
|
||||
(`select ... from all_users` — zero potriviri), deci nu am putut confrunta XML-ul cu antetul lui.
|
||||
|
||||
## 5. Un lucru dovedit pe un fisier real, nu dedus
|
||||
|
||||
Pe `D:\ROA\Efactura\2024_02\VENDING_MASTER_SRL\efactura_20240216_2885_IT_BUL_PLAST_LTD.xml`:
|
||||
|
||||
```
|
||||
BT-5 DocumentCurrencyCode = EUR
|
||||
<cac:AllowanceCharge> ... <cbc:Amount currencyID="RON">539.82</cbc:Amount>
|
||||
<cbc:AllowanceTotalAmount currencyID="EUR">539.82</cbc:AllowanceTotalAmount>
|
||||
```
|
||||
|
||||
Aceeasi suma apare o data ca `RON` si o data ca `EUR` — `currencyID` e hardcodat `"RON"` la
|
||||
`xmlefactura.prg:778`, in timp ce restul documentului foloseste `mmoneda`. Doua precizari care
|
||||
schimba interpretarea:
|
||||
|
||||
- fisierul de pe disc este **identic pe SHA256** cu cel din
|
||||
`...\TRIMISE\4172939206_3250292036.zip`, deci e exact ce a plecat la ANAF;
|
||||
- si a fost **acceptat**. Respingerea din `...\ERORI\4172675286_3249814520.zip` e a unei incarcari
|
||||
*anterioare* si e pentru alte doua reguli (`BR-CO-15` si `BR-CL-04` — cod de moneda invalid,
|
||||
probabil `EURO` in loc de `EUR`, ceea ce explica linia de corectie `xmlefactura.prg:267`).
|
||||
|
||||
Deci `currencyID="RON"` pe factura in valuta e o **neconformitate reala si activa in cod azi**, dar
|
||||
nu am dovada ca ar cauza o respingere.
|
||||
|
||||
## 6. Ce inseamna pentru #13
|
||||
|
||||
Repartizarea proportionala pe cote — recomandarea din raportul principal — **rezolva si problema de
|
||||
aici**, fara nicio garda in plus: daca discountul se sparge pe cotele care exista efectiv pe factura,
|
||||
fiecare bucata mosteneste `id_jtva_coloana` si `expltva` ale grupului ei, grupul orfan dispare, iar
|
||||
`agettipcota(1)` nu mai poate fi ambiguu. Subscriu, cu doua completari:
|
||||
|
||||
1. **Pseudo-liniile de discount trebuie sa primeasca `id_jtva_coloana` al grupului pe care il reduc**,
|
||||
nu doar cota. Cota singura nu ajunge — cheia de grupare are cinci campuri, iar `expltva` se
|
||||
completeaza tot prin `id_jtva_coloana`. O implementare care seteaza doar `proc_tva` ar lasa
|
||||
grupul orfan exact acolo unde e azi.
|
||||
2. **Regula traieste in doua locuri** — VFP (`Calculate Max(proc_tvav)`) si PL/SQL
|
||||
(`MAX(ROUND(discount * (proc_tvav - 1), ...))` in `recalculeaza_totaluri_vanzari`). Daca se
|
||||
schimba doar una, `VANZARI.TOTAL_TVA` si TVA-ul din XML vor diferi pe facturile cu cote mixte.
|
||||
|
||||
## 7. Ce ramane deschis
|
||||
|
||||
- Scenariul de la punctul 2 **nu e reprodus prin program**, ci dedus din cod + semantica `IIF`
|
||||
masurata. O confirmare ar cere o factura scutita cu discount de document, introdusa manual.
|
||||
- Ordinea randurilor in `C_TVA_FACTURA` dupa `GROUP BY` (deci ce alege `agettipcota(1)` intre `E` si
|
||||
`Z`) nu e garantata de VFP si nu am fixat-o experimental. Am presupus `scutit=0` inaintea lui
|
||||
`scutit=1`; daca e invers, categoria alocarii iese `E` in loc de `Z` — tot gresita, dar altfel.
|
||||
- Validatorul ANAF **online** curent nu a fost testat (interdictie). Tot ce spun despre „nu e
|
||||
respins" se refera la DUKIntegrator 2022 de pe disc.
|
||||
- Nu am verificat calea in **valuta** (`prelucreaza_factura_valuta`) linie cu linie.
|
||||
216
docs/cercetare/discount_in_rapoarte_si_efactura.md
Normal file
216
docs/cercetare/discount_in_rapoarte_si_efactura.md
Normal file
@@ -0,0 +1,216 @@
|
||||
# Discount pe linie (DISCOUNT_UNITAR) in afara formularului de facturare — rapoarte .frx, eFactura, SAF-T
|
||||
|
||||
Cercetare read-only pentru S4c (plan_13). Nu s-a modificat niciun fisier, nu s-a rulat git_sync.
|
||||
|
||||
## Verdict (5-10 randuri)
|
||||
|
||||
Niciun raport tiparit de factura (`factura.fr2`, `facturatip*.fr2`, `factura_val*.fr2`,
|
||||
`invoice*.fr2`, `proforma*.fr2`) nu afiseaza discountul pe linie ca coloana separata — nici ca
|
||||
valoare, nici ca procent. Rapoartele tiparesc `pretftva` (pret unitar) si `valftva` (valoare linie)
|
||||
care sunt **deja nete de discount** (calculate ca `pretftva-discountftva` / `Sum(valdiminuatftva)`
|
||||
in cursorul intermediar `crsfacttemp`, construit de `prelucreaza_factura`/`creeaza_crsfacttemp` din
|
||||
`ofacturare_comun.prg`). eFactura (`xmlefactura.prg`) foloseste **exact acelasi cursor** —
|
||||
comentariul din cod chiar spune asta explicit — deci `LineExtensionAmount`/`PriceAmount` trimise la
|
||||
ANAF sunt aceleasi valori nete, cu optiunea (dezactivata implicit, din flag-uri globale) de a
|
||||
adauga si un `cac:AllowanceCharge` informativ pe linie. SAF-T (D406): **nu exista niciun generator
|
||||
D406/SAF-T in ROAFACTURARE** — exista doar tabele de nomenclator cu prefix `saft_` (coduri TVA/plata)
|
||||
folosite pe partea de achizitii, fara legatura cu `DISCOUNT_UNITAR`.
|
||||
|
||||
**Raspuns la intrebarea care conteaza**: daca discountul pe linie devine editabil direct in grid,
|
||||
**nu se strica nimic in codul rapoartelor/eFacturii** — ele citesc oricum campurile finale
|
||||
(`valdiminuatftva`/`discountftva`/`discount_unitar`) din cursor, indiferent cum au fost populate
|
||||
(dialog separat vs. editare in grid). **Riscul real e in alta parte**: exista deja azi o cale prin
|
||||
care discountul e editabil direct in grid (`vdiscountftva`, pe factura in valuta) care **nu
|
||||
declanseaza recalcularea** lui `valdiminuatftva`/`valdiminuatctva` — vezi `docs\cercetare\discount_verificare2.md`
|
||||
punctul 3, ultimul paragraf. Daca S4c extinde editarea in grid la toate coloanele de discount fara
|
||||
sa cablaje si recalculul, rapoartele si eFactura vor tipari/trimite **valori vechi** (stale), pentru
|
||||
ca ambele citesc `valdiminuatftva`, nu `discount_unitar` direct.
|
||||
|
||||
## 1. Rapoartele .frx/.fr2 tiparite
|
||||
|
||||
**Cautare**: `Grep 'DISCOUNT'` si apoi `Grep 'disc'` (case-insensitive) in toate `.fr2` din
|
||||
`COMUN\Rapoarte` cu glob `*factur*.fr2`, `*proforma*.fr2`, `*invoice*.fr2` — **zero potriviri** in
|
||||
toate cele (factura.fr2, facturatip.fr2, facturatip_a5.fr2, facturatip_cuchit.fr2, factura_a5.fr2,
|
||||
factura_chit.fr2, factura_val.fr2, factura_val_a5.fr2, invoice.fr2, invoice_a5.fr2, proforma.fr2,
|
||||
proforma_orig.fr2, proforma_val.fr2, proforma_val_orig.fr2). Singurele 2 fisiere `.fr2` din toata
|
||||
`COMUN\Rapoarte` care contin "DISCOUNT" sunt `rap_nir_materiale.fr2` si `rap_nir_marfuri.fr2` —
|
||||
rapoarte de NIR (receptie), nu facturi emise.
|
||||
|
||||
**Ce se tipareste per linie** (confirmat in `factura.fr2`):
|
||||
```
|
||||
1002: <expr><![CDATA[formateaza(cantitate,30,gnPCant)]]>
|
||||
1014: <expr><![CDATA[formateaza(pretftva,14,gnPPretV)]]>
|
||||
1026: <expr><![CDATA[formateaza(valftva,14,gnPc)]]>
|
||||
1062: <expr><![CDATA[PADL(ALLTRIM(STR((proc_tva-1)*100)),2,[ ])+'%']]> -- procent TVA, nu discount
|
||||
```
|
||||
Deci: cantitate, pret unitar, valoare linie, procent TVA. Nici discount valoric, nici procent
|
||||
discount.
|
||||
|
||||
**De unde vin `pretftva`/`valftva` — si de ce sunt deja nete**: cursorul de tiparire
|
||||
(`crsfacttemp`) e construit de `Procedure prelucreaza_factura` (`COMUN\programe\ofacturare_comun.prg:1055-1059`),
|
||||
apelata din `COMUN\programe\ofacturare.prg:1887`:
|
||||
```
|
||||
prelucreaza_factura([crsfactura], [crsfacturaset], [crsfacturafinala], poDate.discount_evidentiat, poDate.in_valuta, lnPretListAviz, poDate.nListareDetaliata)
|
||||
```
|
||||
In interior, `Do Case` pe `tnDiscountEvidentiat`/`lnPretListAviz` (`ofacturare_comun.prg:1156-1248`).
|
||||
Cazul comun, `tnDiscountEvidentiat = 0` (checkbox-ul "Se pune in evidenta discount-ul..." NEBIFAT —
|
||||
comportamentul implicit):
|
||||
```
|
||||
1182: Sum(cantitate) As cantitate,pretftva-discountftva As pretftva,;
|
||||
...
|
||||
1186: Sum(valdiminuatftva) As valftva,Sum(valdiminuattva) As valtva,;
|
||||
```
|
||||
adica pretul tiparit = pret brut minus discountul pe unitate, iar valoarea tiparita = suma
|
||||
`valdiminuatftva` (deja calculata la adaugarea articolului ca `pretftva - discount_unitar`, cf.
|
||||
`ofacturare.vc2:13126`, deja documentat in `discount_pe_articol.md` sectiunea 3). Simetric pentru
|
||||
varianta cu TVA la `:1226-1230`.
|
||||
|
||||
**Cazul `tnDiscountEvidentiat = 1`** (checkbox bifat — discountul "se pune in evidenta"):
|
||||
```
|
||||
1165: pretftva,Nvl(codbare,... -- pretftva RAMANE brut, nu se scade discountftva
|
||||
1166-1167: Sum(valftva) As valftvai,... Sum(valftva) As valftva,Sum(valtva) As valtva,
|
||||
1168: discountftva,Sum(valdiscountftva) As valdiscountftva,Sum(valdiscounttva) As valdiscounttva,
|
||||
```
|
||||
Aici `pretftva`/`valftva` tiparite raman **brute** (neta de discount), iar `discountftva`/
|
||||
`valdiscountftva`/`valdiscounttva` sunt calculate in cursor **dar nu sunt tiparite de niciun .frx**
|
||||
(confirmat, zero hit pe "disc" in .fr2). Practic, cand discountul e "pus in evidenta", pretul si
|
||||
valoarea linie tiparite pe factura NU reflecta discountul per linie — discountul apare doar ca linie
|
||||
separata de document (randul-sentinela `denumire = Replicate('Z',20)`, populat din
|
||||
`Thisform.nbazafdiscount`/`nbazafdiscountval` la `ofacturare.vc2:14534,14591,18532,18602` si
|
||||
`ofacturare_comun.prg:1891`) — asta e insa discountul **de document** (`VANZARI.DISCOUNT`), nu
|
||||
`DISCOUNT_UNITAR` pe linie. Nu am gasit nicio dovada ca acest mod (`discount_evidentiat=1`) ar fi
|
||||
folosit uzual — e o optiune existenta, documentata deja in `discount_pe_articol.md` punctul 3 ca
|
||||
schimband "doar mecanismul aritmetic", dar aici se vede consecinta suplimentara: **si ce se
|
||||
tipareste**.
|
||||
|
||||
**Fara impartiri la pret zero pe randul de discount**: unde raportul calculeaza procent
|
||||
(`proc_disc`), sursa e protejata explicit:
|
||||
```
|
||||
1184-1185 (ofacturare_comun.prg): Iif(cu_tva=0,Iif(pretftva<>0,Round(discountftva/pretftva*100,2),0),;
|
||||
Iif(pretctva<>0,Round(discountctva/pretctva*100,2),0))
|
||||
```
|
||||
deci nu exista risc de impartire la zero — dar, ca observat mai sus, `proc_disc` nu e tiparit
|
||||
nicaieri in .frx-urile de factura verificate (e calculat doar pentru consum intern/eFactura).
|
||||
|
||||
## 2. eFactura (ANAF/UBL) — `xmlefactura.prg`
|
||||
|
||||
**Concluzie**: eFactura foloseste **acelasi cursor de linii ca cel de la tiparire**, explicit
|
||||
documentat in cod:
|
||||
```
|
||||
oexport.prg:1590: * tcCursorLiniiFactura = numele cursorului cu liniile facturii, asa cum arata la listare
|
||||
```
|
||||
Lantul: `ofacturare.prg:2126` -> `goExport.export2xml_efactura(poDate, m.lcCursoreFactura, ...)` cu
|
||||
`lcCursoreFactura` = `lcCursorFacturaTemp` (= `'crsFacturaFinala'`, `ofacturare.prg:1892`, sau
|
||||
`'crsfacturafinalaval'` pentru valuta) -> `xmlefactura.prg:231`:
|
||||
```
|
||||
Select a.*, ... From (m.tcCursorLiniiFactura) a Left Join cJTVAVanzariTemp b ... Into Cursor C_IES_FORM Readwrite
|
||||
```
|
||||
Deci `C_IES_FORM` (sursa liniilor XML) e construit direct din cursorul de tiparire — aceleasi
|
||||
`pretftva`/`pretftvai`/`valftva`/`valftvai`/`proc_disc` descrise la punctul 1, cu aceeasi dependenta
|
||||
de `poDate.discount_evidentiat`.
|
||||
|
||||
**Ce se trimite pe linie**:
|
||||
- `cbc:LineExtensionAmount` (BT-131) = `C_IES_FORM.valftva` — `xmlefactura.prg:935-937`.
|
||||
- `cbc:PriceAmount` = `C_IES_FORM.pretftva` — `xmlefactura.prg:1043-1046`.
|
||||
- Optional (doar daca flag global `gnEFACTURA_XML_DISC_PLISTA_LINIE = 1` SI `pretftvai > pretftva`):
|
||||
un `cac:AllowanceCharge` la nivel de linie cu `Amount = valftvai-valftva`,
|
||||
`BaseAmount = valftvai`, `MultiplierFactorNumeric = proc_disc` — `xmlefactura.prg:952-971`.
|
||||
- Optional (flag separat `gnEFACTURA_XML_DISC_PLISTA_ART = 1`): un `cac:AllowanceCharge` la nivel de
|
||||
`cac:Price` cu discountul unitar (`ABS(pretftvai-pretftva)`) — `xmlefactura.prg:1054-1067`.
|
||||
|
||||
Ambele flag-uri sunt verificate cu `TYPE(...) = 'N' AND ... = 1` — daca variabila globala nu exista
|
||||
sau e 0 (implicit), **nu se trimite niciun `AllowanceCharge` pe linie**; discountul e vizibil pentru
|
||||
ANAF doar implicit, prin faptul ca `PriceAmount * InvoicedQuantity = LineExtensionAmount` (adica
|
||||
pretul e deja net). Nu am gasit unde sunt setate global aceste doua variabile (`gnEFACTURA_XML_DISC_PLISTA_LINIE`/`_ART`) —
|
||||
posibil optiuni de firma necautate in acest fisier; nu afecteaza concluzia principala.
|
||||
|
||||
**Discount de document, separat**: exista o eticheta euristica in `C_IES_FORM`
|
||||
(`Iif('DISCOUNT' $ Upper(a.denumire) And a.pretftva < 0, 1, 0) As discount`, `xmlefactura.prg:230`)
|
||||
care detecteaza randuri-pseudo-articol de discount/taxa (denumire contine "DISCOUNT", pret negativ)
|
||||
si le exclude din liniile normale (`Scan For discount = 0`, `:901`), agregandu-le separat intr-un
|
||||
`cac:AllowanceCharge` la nivel de document (`:756-793`, motiv "Discount", cod 95). Acesta e
|
||||
discountul de document, nu `DISCOUNT_UNITAR` pe linie — mecanism diferit de randul-sentinela
|
||||
`Replicate('Z',20)` folosit la tiparire (punctul 1).
|
||||
|
||||
**Nicio validare care ar respinge discount > pret**: n-am gasit nicio verificare explicita in
|
||||
`xmlefactura.prg` care sa blocheze o linie cu `discount_unitar` mai mare ca pretul (ar rezulta
|
||||
`pretftva` negativ pe linie individuala; codul are doar o corectie generala pentru
|
||||
`pretftva < 0` la nivel de linie completa, `xmlefactura.prg:236-239`, care inverseaza semnul
|
||||
cantitate/pret — nu specifica pentru discount).
|
||||
|
||||
## 3. SAF-T (D406)
|
||||
|
||||
**Concluzie: nu exista implementare SAF-T/D406 in ROAFACTURARE.** Cautat explicit:
|
||||
- `Grep 'SAF-T|SAFT|D406|declaratie406|GenereazaSAFT|GenereazaD406'` in tot `COMUN\programe` si
|
||||
`COMUN` — nicio potrivire pe un generator de fisier D406/SAF-T XML.
|
||||
- `Glob '**/*saft*'` pe tot proiectul si pe `COMUN` — zero fisiere cu "saft" in nume.
|
||||
- Singurele aparitii reale (nu fals-pozitive de tip "safe") sunt:
|
||||
- `oinit_optiuni.prg:523-524`: `gl406 = (TYPE('gnD406') = 'N' and m.gnD406 = 1)` — un flag "firma
|
||||
are activata SAFT 406", comentat "Firma are activata SAFT 406".
|
||||
- `oproceduri_comune.prg:889-896,6083,6151`, `ointroduceri.prg:1597`: `saft_taxtable` si
|
||||
`saft_mecanisme_plati` — tabele de nomenclator (coduri de TVA/mecanism de plata compatibile
|
||||
SAF-T) folosite pe **partea de achizitii** (limitare deducere TVA la introducerea facturilor de
|
||||
achizitie), nu pe vanzari/facturare.
|
||||
- `oproceduri_comune.prg:6066-6067,6137-6138,6195`: comentarii care mentioneaza "Taxa SAFT tip"
|
||||
ca denumire pentru codurile de TVA, tot pe achizitii.
|
||||
- Niciuna din aceste aparitii nu are legatura cu `DISCOUNT`/`DISCOUNT_UNITAR` — cautarea `DISCOUNT`
|
||||
in fisierele care contin "saft" (`oproceduri_comune.prg`, `ointroduceri.prg`, `pmenu.prg`,
|
||||
`oinit_optiuni.prg`) nu a gasit nicio intersectie intre cele doua seturi de rezultate.
|
||||
|
||||
**Interpretare**: `gl406` pare sa activeze doar validari/coduri suplimentare pentru conformitate
|
||||
SAF-T pe partea de achizitii (poate pentru ca alt produs din suita, ROACONT, genereaza efectiv
|
||||
D406 din datele contabile) — ROAFACTURARE nu produce singur un fisier D406.
|
||||
|
||||
## 4. Alte consumatori care ar presupune discount uniform pe document
|
||||
|
||||
Nu am gasit vreun raport/export care sa presupuna un discount unic pe tot documentul in sensul de
|
||||
"aceeasi valoare/procent pe toate liniile" — mecanismul de discount de document
|
||||
(`VANZARI.DISCOUNT`, randul `Replicate('Z',20)`) e tratat ca **valoare agregata separata**, adaugata
|
||||
ca linie proprie (in tiparire) sau ca `AllowanceCharge` de document (in eFactura), nu redistribuita
|
||||
implicit pe liniile existente — cu o exceptie: `xmlefactura.prg` are logica de **distribuire**
|
||||
explicita a discountului/taxelor de document pe articole cand bifa `chkDistribuieDiscount`/
|
||||
`llDistribuieDiscountTaxe` e activa (`xmlefactura.prg:12189-12320`, `:12534-12790`) — asta insa
|
||||
opereaza pe discountul de document, nu presupune ca discountul de linie (`DISCOUNT_UNITAR`) e
|
||||
uniform; dimpotriva, distribuie proportional cu valoarea fiecarei linii, deci suporta discount
|
||||
diferit per linie din start.
|
||||
|
||||
## Ce ar strica S4c, concret
|
||||
|
||||
**Nu se strica codul rapoartelor sau al eFacturii** — ambele citesc valori finale din cursor
|
||||
(`valdiminuatftva`, `discountftva`, `pretftva` deja net), indiferent de sursa UI a discountului.
|
||||
|
||||
**Se poate strica invariantul pe care se bazeaza**, daca editarea in grid nu recalculeaza:
|
||||
- **Dovada ca gaura exista deja azi**: pe factura in valuta, coloana `vdiscountftva` e editabila
|
||||
direct in grid (`ofacturare.vc2:12340-12345`, fara `ReadOnly`) dar **fara niciun handler
|
||||
`Valid`/`InteractiveChange`** care sa recalculeze `vvaldiminuatftva`/totalurile — confirmat cautat
|
||||
explicit in `docs\cercetare\discount_verificare2.md` punctul 3, ultimul paragraf ("editarea directa
|
||||
in grid NU declanseaza recalcularea automata a `vvaldiminuatftva`/totaluri, doar modifica valoarea
|
||||
bruta in `crsfactura`").
|
||||
- **De ce conteaza pentru rapoarte/eFactura**: `valdiminuatftva`/`valdiminuatctva` (nu
|
||||
`discount_unitar` brut) sunt campurile citite de `prelucreaza_factura`/`creeaza_crsfacttemp`
|
||||
pentru `valftva` tiparit si trimis la ANAF (punctele 1 si 2 de mai sus). Daca operatorul modifica
|
||||
discountul direct in grid si acel camp nu se recalculeaza, factura tiparita si XML-ul ANAF vor
|
||||
arata **valoarea veche** (dinainte de editare) — o discrepanta reala intre ce vede operatorul in
|
||||
grid si ce se tipareste/trimite.
|
||||
- **Concluzie pentru plan**: daca S4c extinde editarea de discount la toate coloanele din grid
|
||||
(inclusiv `discountctva`, azi read-only pe lei), trebuie cablat un recalcul echivalent cu
|
||||
`frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`) pe evenimentul de editare
|
||||
din grid — altfel riscul de mai sus (deja prezent azi doar pe valuta) se extinde la toate
|
||||
facturile in lei.
|
||||
|
||||
## Ramas de verificat
|
||||
|
||||
- Valorile implicite ale flag-urilor globale `gnEFACTURA_XML_DISC_PLISTA_LINIE` si
|
||||
`gnEFACTURA_XML_DISC_PLISTA_ART` (unde sunt setate ca optiune de firma) — nu am cautat sursa lor,
|
||||
doar am confirmat ca cu `TYPE(...) <> 'N'` (nedefinite) comportamentul e "fara AllowanceCharge pe
|
||||
linie".
|
||||
- Nu am verificat pe date reale (Oracle/XML generat) un caz cu `discount_evidentiat=1` si
|
||||
`DISCOUNT_UNITAR` populat simultan, ca sa confirm empiric (nu doar din citirea codului) ca factura
|
||||
tiparita arata pretul brut.
|
||||
- Nu am urmarit `crsfacturafinalaval` (varianta valuta a cursorului final) linie cu linie — am
|
||||
presupus ca urmeaza acelasi patron ca `crsfacturafinala`/`crsfacttemp` (cursorul in lei), pe baza
|
||||
simetriei campurilor `vpretftva`/`vvalftva`/`vdiscountftva` gasite deja documentate in
|
||||
`discount_verificare2.md` si `discount_pe_articol.md`; n-am reverificat separat sursa SQL pentru
|
||||
varianta valuta.
|
||||
- Nu am identificat un al doilea produs (ROACONT?) care sa genereze efectiv D406 din datele scrise
|
||||
de ROAFACTURARE — presupunerea din sectiunea 3 e speculativa, nu confirmata cu cod.
|
||||
185
docs/cercetare/factura_retur_document.md
Normal file
185
docs/cercetare/factura_retur_document.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Cercetare: factura de retur ca document de sine statator (tip 8/9) + aviz retur (24)
|
||||
|
||||
Corectie fata de `retur_si_lista_preturi.md`: acea cercetare a documentat corect `But_retur`
|
||||
(retur de articole *in interiorul* unei facturi normale, tip 1/5/7/10 — selectie **per articol**).
|
||||
Aici e documentat mecanismul **separat**: factura de retur ca document propriu (tip 8 = retur lei,
|
||||
tip 9 = retur valuta), unde utilizatorul alege **facturile sursa la nivel de document**, iar linia
|
||||
de articole se populeaza integral din acele facturi.
|
||||
|
||||
## 1. Punctul de intrare si cursorul Oracle
|
||||
|
||||
Tile-ul de pe ecranul principal de facturare, `Page2.Cw1` (`COMUN\clase\ofundal_facturare.vc2:882-884`):
|
||||
```
|
||||
PROCEDURE Page2.Cw1.do_actiune
|
||||
DO facturare_lista_de_preturi IN oproceduri_facturare.prg
|
||||
ENDPROC
|
||||
```
|
||||
`facturare_lista_de_preturi` (`COMUN\programe\oproceduri_facturare.prg:114-116`) = `Do politica.mpr`,
|
||||
care ruleaza meniul shortcut generat din `Meniuri\politica.mn2:14-15,45-46`:
|
||||
```
|
||||
DEFINE BAR 2 OF Shortcut PROMPT "\<Retur factura in lei"
|
||||
ON SELECTION BAR 2 OF Shortcut factureaza(8)
|
||||
...
|
||||
DEFINE BAR 9 OF Shortcut PROMPT "Re\<tur factura in valuta"
|
||||
ON SELECTION BAR 9 OF Shortcut factureaza(9)
|
||||
```
|
||||
Deci `factureaza(8)` / `factureaza(9)` sunt apelate direct, fara `toFactura` (nu e copiere).
|
||||
Comentariul din `factureaza` (`COMUN\programe\ofacturare.prg:134-135`) confirma explicit numerotarea:
|
||||
```
|
||||
** (25007,8) - retur factura in lei ( 25017 )
|
||||
** (25008,9) - retur factura in valuta ( 25018 )
|
||||
```
|
||||
In `Do Case` pe `tnTip` din `factureaza` (`ofacturare.prg:306-307`):
|
||||
```
|
||||
Case Inlist(tnTip, 8, 9, 24) && 8,9 = facturi de retur, 24 = aviz retur
|
||||
lcSqlCursor = [{call ] + gcS + [.pack_facturare.cursor_retur(?poDate.in_valuta,?poDate.listaid,?gnIdUtil)}]
|
||||
```
|
||||
executat prin `goExecutor.oExecute(lcSqlCursor, [crsarticole])` (`ofacturare.prg:310-311`) — acelasi
|
||||
cursor `crsarticole` folosit si de `cursor_preturi`/`cursor_comanda`/`cursor_contract` pentru
|
||||
celelalte tipuri. Nota: `Do Case` are inaintea acestei ramuri o ramura separata `Case m.llCopiere`
|
||||
(`ofacturare.prg:268`) care foloseste `pack_facturare.cursor_retur_document(...)` — dar `llCopiere`
|
||||
e `.T.` doar cand `factureaza()` primeste un al doilea parametru `toFactura` (obiect), adica la
|
||||
copiere de document, nu la intrarea normala prin meniu pentru tip 8/9 (`llCopiere = (Type('toFactura')='O')`,
|
||||
`ofacturare.prg:111`). Vezi si punctul 6.
|
||||
|
||||
`nIdTipDoc` = 5 (FACTURA, `ofacturare.prg:193`, `tnTip<21`), formularul de date antet este
|
||||
`frm_date_factura` (`ofacturare.prg:222-223`, acelasi caz `tnTip<21`).
|
||||
|
||||
## 2. Alegerea facturilor sursa
|
||||
|
||||
Formularul `frm_date_factura` (`COMUN\clase\ofacturare.vc2:8482`) are metoda dedicata
|
||||
`do_cauta_facturi` (`ofacturare.vc2:9173-9212`):
|
||||
```
|
||||
Case Empty(Nvl(poDate.id_client,0))
|
||||
amessagebox("Nu ati ales clientul!",...)
|
||||
Case Empty(Nvl(poDate.id_valuta,0)) And poDate.tip = 9
|
||||
amessagebox("Nu ati ales valuta!",...)
|
||||
OTHERWISE
|
||||
lcXMLFacturi = caut_facturi_multiple_client(poDate.id_client,poDate.in_valuta,poDate.id_valuta,.T.)
|
||||
If !Empty(lcXMLFacturi) and gnButon = 1
|
||||
Xmltocursor(lcXMLFacturi, "crsFacturiTemp")
|
||||
...
|
||||
poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")
|
||||
poDate.descriere = cursor2lista("crsFacturiTemp", "numar_act", ",")
|
||||
...
|
||||
poDate.text_aditional = Iif(poDate.tip=8,[RETUR FACTURA ],[REFUND INVOICE FOR ]) + poDate.descriere
|
||||
```
|
||||
Dialogul e `caut_facturi_multiple_client` (`COMUN\programe\oproceduri_facturare.prg:2091-2121`),
|
||||
un browse generic `cauta_alfa` cu titlu **"Alegeti facturile (mouse-click pe numar sau apasati SPACE)"**
|
||||
(`:2104`, selectie multipla — `lnTipReturn = Iif(tlFacturiMultiple,1,0)`, apelat cu `tlFacturiMultiple=.T.`).
|
||||
Criteriile SQL (`:2108-2111`):
|
||||
```
|
||||
lcSelect = [select serie_act,numar_act,data_act,dataora,id_vanzare from ] + gcS + [.fact_vfacturi ]
|
||||
lcFiltruOriginal = [sters=0 and tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) ] + gcCondSucursala +
|
||||
lcFiltruPart + lcFiltruValuta
|
||||
```
|
||||
adica **client** (`id_part`, obligatoriu ales inainte) si **valuta** (`in_valuta`/`id_valuta`, doar
|
||||
daca `tnInValuta` e setat) — nu exista filtru SQL pe perioada sau serie/numar in interogare (coloanele
|
||||
`Serie act, Numar act, Data, Data inreg.` sunt doar afisate/sortabile in browse-ul generic, filtrarea
|
||||
pe ele e comportament generic al `cauta_alfa`, neverificat mecanismul intern). Sursa exclude explicit
|
||||
tipurile de retur (`tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)`) — nu se poate face retur dintr-un retur.
|
||||
|
||||
**Selectie multipla**: se aduna prin `cursor2lista("crsFacturiTemp","id_vanzare",",")` intr-un
|
||||
singur string CSV in `poDate.listaid` (id-urile facturilor alese), respectiv
|
||||
`cursor2lista(...,"numar_act",",")` in `poDate.descriere` (afisat apoi pe antetul liniilor si in
|
||||
titlul formularului de articole, `ofacturare.vc2:15022-15023`: `[ * Retur pentru facturile : ] + poDate.descriere`).
|
||||
|
||||
Validare obligatorie inainte de a continua (`frm_date_factura.inainte_de_do_termin`, `ofacturare.vc2:9523-9526`):
|
||||
```
|
||||
Case Empty(Nvl(poDate.descriere,[])) And Inlist(poDate.tip,8,9)
|
||||
amessagebox("Nu ati ales factura/facturile pentru care se face returul!",48,"Atentie")
|
||||
```
|
||||
|
||||
## 3. Popularea liniilor: gestiune si pret
|
||||
|
||||
`pack_facturare.cursor_retur` e un wrapper subtire peste implementarea reala
|
||||
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:3943-3956`):
|
||||
```
|
||||
PROCEDURE cursor_retur(V_IN_VALUTA, V_LISTAID, V_ID_UTIL, V_CURSOR) IS
|
||||
V_COPIERE NUMBER := 0;
|
||||
V_PROFORMA NUMBER := 0;
|
||||
BEGIN
|
||||
pack_facturare.cursor_retur_document(V_IN_VALUTA, V_LISTAID, V_COPIERE, V_PROFORMA, V_ID_UTIL, V_CURSOR);
|
||||
END;
|
||||
```
|
||||
`cursor_retur_document` (`:3958-4071`) selecteaza direct din `VANZARI_DETALII` (liniile facturilor
|
||||
originale), filtrat pe `A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)` unde `CRS` e lista de id-uri
|
||||
din `V_LISTAID` (= `poDate.listaid`, adica exact facturile alese la pasul 2, `:4059-4064`):
|
||||
```sql
|
||||
FROM VANZARI_DETALII A1 LEFT JOIN VANZARI_CURSURI A2 ON A1.ID_VANZARE = A2.ID_VANZARE ...
|
||||
WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)
|
||||
```
|
||||
Coloane relevante, confirmate ca provin direct din linia facturii originale:
|
||||
- **gestiune**: `nvl(A.ID_GESTIUNE, 0) as ID_GESTIUNE` (`:4034`), din `A1.ID_GESTIUNE` (`VANZARI_DETALII.ID_GESTIUNE`
|
||||
al liniei originale, `:4055`) — confirma afirmatia lui Marius: gestiunea vine din factura sursa.
|
||||
- **pret de achizitie**: `A.PRET_ACHIZITIE` (`:4035`), din `A1.PRET_ACHIZITIE` (`:4056`) —
|
||||
preluat neschimbat din linia originala, fara recalcul de curs.
|
||||
- **pret**: coloana `PRET` (`:4016-4024`) e pretul de vanzare al liniei originale, recalculat pe
|
||||
cursul valutar daca moneda nu e nationala (`ROUND(A.CURS * ROUND(A.PRET,...) / A.MULTIPLICATOR, ...)`,
|
||||
altfel `ROUND(A.PRET,...)`; plus `PRET_VAL` (`:4025-4030`) — valoarea in valuta straina, cand e cazul.
|
||||
- `GESTIONABIL` (`:4002-4009`) pentru cazul `V_COPIERE=0` (retur, ramura efectiv folosita de
|
||||
`cursor_retur`): `A.GESTIONABIL` = `NVL2(A1.ID_GESTIUNE,1,0)` (subselect intern, `:4048`) — gestionabil
|
||||
doar daca linia originala avea gestiune.
|
||||
|
||||
Concluzie Q3: **ambele preturi** trec prin, atat cel de vanzare (`PRET`/`PRET_VAL`, ajustat pe curs)
|
||||
cat si cel de achizitie (`PRET_ACHIZITIE`, neschimbat) — plus gestiunea originala (`ID_GESTIUNE`).
|
||||
Numele coloanelor Oracle -> cate un camp cu acelasi nume in cursorul VFP `crsarticole` (maparea VFP
|
||||
exacta camp-cu-camp nu a fost trasata pana in `crsfactura`; nu era necesara pentru raspuns).
|
||||
|
||||
## 4. Ce se poate face manual (`frm_facturare_articole`, `COMUN\clase\ofacturare.vc2:10968`)
|
||||
|
||||
- **Stergere linie**: `do_sterge` (`:14608-14693`) nu e restrictionat pe tip; pentru retur
|
||||
(`Case Inlist(poDate.tip,8,9,24)`, `:14658-14659`) cantitatea stearsa se reintoarce in cursorul
|
||||
sursa (`Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c`) — deci
|
||||
**da, se pot sterge linii aduse**, iar cantitatea redevine disponibila pentru re-adaugare.
|
||||
- **Retur partial (modificare cantitate)**: coloana de cantitate din grila sursa isi schimba titlul
|
||||
in "Cant. max. de returnat" pentru tip 8/9 (`:15236-15239`); validarea in `do_verifica_articol`
|
||||
(`:14743-14754`, `llRetur = Inlist(poDate.tip,8,9,24)`) respinge doar cazul in care cantitatea
|
||||
ceruta ar depasi maximul returnabil — utilizatorul poate introduce orice cantitate <= maxim,
|
||||
deci **da, retur partial e posibil**. `do_modifica` (`:13746-13914`) trateaza explicit
|
||||
`Inlist(poDate.tip,8,9,24)` la `:13788,13895` fara blocaj suplimentar.
|
||||
- **Adaugare linie care NU e in facturile sursa**: `do_adauga_articol` (`:12813-13086`) preia
|
||||
articolul mereu din cursorul sursa al gridului (`lcCursor = [crsarticole]`, apoi
|
||||
`Select (lcCursor) / Scatter Name poArticol`, `:12843-12851`) — pentru tip 8/9 acest `crsarticole`
|
||||
e chiar rezultatul `cursor_retur` de la punctul 3, deci contine **doar** liniile facturilor alese.
|
||||
Nu exista pe acest formular o cale de a alege un articol din lista de preturi completa cand
|
||||
`poDate.tip` e 8/9 (spre deosebire de contract/comanda unde `crsarticole` ramane lista de preturi
|
||||
libera) — **nu se poate adauga o linie in afara facturilor sursa**, prin design-ul continutului
|
||||
cursorului, nu printr-o validare explicita de interzicere.
|
||||
|
||||
## 5. Aviz de retur (tip 24)
|
||||
|
||||
Acelasi mecanism de baza: `Inlist(tnTip,8,9,24)` la `ofacturare.prg:306-307` (acelasi
|
||||
`pack_facturare.cursor_retur`) si aceleasi ramuri `Inlist(poDate.tip,8,9,24)` in
|
||||
`frm_facturare_articole` (`do_adauga_articol:12863`, `do_alege_stoc:13230`, `do_modifica:13788,13895`,
|
||||
`do_verifica_articol:14743`, `do_sterge:14658`). Diferenta: la antet, `nIdTipDoc` = 6 (AVIZ, ramura
|
||||
`Otherwise` la `ofacturare.prg:194-195`, pentru ca 24 nu e `<21` si nu e in `Inlist(45,48,49,51,52)`),
|
||||
iar formularul de date e `frm_date_aviz` (`ofacturare.prg:225`, ramura `Otherwise`), nu
|
||||
`frm_date_factura`. Punctul de intrare pentru tip 24: `emitere_aviz_clienti` cu `tnTip=7`
|
||||
(`COMUN\programe\oproceduri_facturare.prg:221-222`, `factureaza(24) && Retur aviz`), apelat din
|
||||
tile-ul `Page3.Cw1` -> `aviz_clienti.mpr` (neverificat detaliat, nu s-a insistat conform cerintei).
|
||||
|
||||
## 6. Relatia cu `But_retur`
|
||||
|
||||
Mecanisme **complet separate**, confirmate pe cod:
|
||||
- **Vizibilitate**: `but_retur` e vizibil doar pentru tip 1/5/7/10 (`ofacturare.vc2:15127`,
|
||||
`This.but_retur.Visible = .T.` in ramura `Case Inlist(poDate.tip,1,5,7,10)`) — pe un document
|
||||
tip 8/9 acest buton nu exista deloc in UI (ramura lui la `Init` e alta, `:15236`).
|
||||
- **Formular de alegere a facturii sursa**: `But_retur` foloseste `caut_facturi_multiple_client_articol`
|
||||
(`oproceduri_facturare.prg:2124-2159`, filtrat suplimentar pe `b.id_articol = ?pnIdArticol` —
|
||||
cautare per-articol, cu titlu simplu "Alegeti factura"), tip 8/9 document foloseste
|
||||
`caut_facturi_multiple_client` (`:2091-2121`, fara filtru pe articol, multi-select, titlu
|
||||
"Alegeti facturile ..."). Doua functii diferite, in acelasi fisier, cu semnaturi aproape identice
|
||||
dar interogari SQL diferite.
|
||||
- **Procedura Oracle de populare a liniilor**: documentul tip 8/9 foloseste
|
||||
`pack_facturare.cursor_retur` la nivel de document intreg (populeaza `crsarticole` cu toate
|
||||
liniile facturilor alese, punctul 3 de mai sus). `But_retur` nu apeleaza `cursor_retur`/
|
||||
`cursor_retur_document` deloc — foloseste `pack_facturare.cursor_gestiuni_articol_retur`
|
||||
(`ofacturare.vc2:13230`, per articol individual, in `do_alege_stoc`) pentru a alege
|
||||
gestiunea/seria/lotul de returnat pentru articolul deja selectat din lista de preturi a facturii
|
||||
normale curente.
|
||||
- **Punct comun**: niciunul direct. Singurul element comun e conventia de semn a cantitatii
|
||||
(`Iif(Inlist(poDate.tip,8,9,24),(-1),1)`, folosita si in `do_alege_stoc` al `But_retur`,
|
||||
`:13279-13309`, si pe formularul `frm_facturare_articole2`, `:17574-17585`) si textul UI
|
||||
("cant. max. de returnat"). Concluzie: sunt doua fluxuri de cod independente care ating aceeasi
|
||||
clasa de formular (`frm_facturare_articole`) dar prin metode si proceduri Oracle diferite.
|
||||
235
docs/cercetare/garda_aviz_facturat.md
Normal file
235
docs/cercetare/garda_aviz_facturat.md
Normal file
@@ -0,0 +1,235 @@
|
||||
# Garda "aviz deja facturat" — verificare pe cod
|
||||
|
||||
Verificare pe cod, fara nicio modificare de fisier. Sursele citate:
|
||||
|
||||
- export pachet: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(mai jos: `EXPORT:<linie>`);
|
||||
- corpul **desfasurat pe DB** (`MARIUSM_AUTO@ROA_CENTRAL`, `all_source`), citit ca sa se confirme ca
|
||||
ce e in export e si ce ruleaza (mai jos: `DB PACK_FACTURARE:<linie>`). **Numerotarea difera**:
|
||||
`sterge_factura` incepe la `EXPORT:5432`, dar la `DB PACK_FACTURARE:4192`. Nu exista offset
|
||||
constant; textul insa e identic cuvant cu cuvant pe zona gardelor.
|
||||
|
||||
---
|
||||
|
||||
## 1. Blocheaza garda de azi si un AVIZ care are deja factura generata din el?
|
||||
|
||||
**DA.** Nu e nevoie de nicio garda noua pentru cazul asta — este exact ce testeaza a doua garda din
|
||||
`sterge_factura`, prin `TIP IN (1, 2)`.
|
||||
|
||||
`EXPORT:5464-5476` = `DB PACK_FACTURARE:4224-4236`:
|
||||
|
||||
```
|
||||
-- ptr. un aviz normal:
|
||||
-- verific daca exista facturi sau avize de retur pe avizul resp.
|
||||
SELECT COUNT(*)
|
||||
INTO V_NR_AVIZE_FACT
|
||||
FROM VANZARI_CORESP
|
||||
WHERE STERS = 0
|
||||
AND ID_VANZARE_AVIZ = V_ID_VANZARE
|
||||
AND TIP IN (1, 2);
|
||||
|
||||
IF V_NR_AVIZE_FACT > 0 THEN
|
||||
RAISE_APPLICATION_ERROR(-20000,
|
||||
'Pe acest aviz s-au emis facturi / aviz de retur. Trebuie sa stergeti mai intai facturile / avizul de retur!');
|
||||
END IF;
|
||||
```
|
||||
|
||||
Cheia e semantica lui `TIP`, citita din **singurul scriitor** al tabelei, ramura cu ramura
|
||||
(`finalizeaza_factura`, `EXPORT:14818-14839`):
|
||||
|
||||
| `VANZARI_CORESP.TIP` | scris cand | `ID_VANZARE_FACT` | `ID_VANZARE_AVIZ` | e retur? |
|
||||
|---|---|---|---|---|
|
||||
| **1** | `ntip = 4` — facturare din aviz (`EXPORT:14823-14826`) | factura | avizul-sursa | **NU** |
|
||||
| 2 | `ntip = 24` — aviz de retur (`EXPORT:14828-14830`) | avizul de retur | avizul original | DA |
|
||||
| 3 | `ntip in (8, 9)` — factura de retur (`EXPORT:14834-14836`) | factura de retur | factura originala | DA |
|
||||
|
||||
Deci `TIP = 1` este **corespondenta aviz -> factura normala**, nu retur. Garda de la `EXPORT:5473`
|
||||
prinde ambele cazuri; textul mesajului chiar o spune ("s-au emis **facturi** / aviz de retur").
|
||||
Formularea din brief ("garda de azi blocheaza documentul care are facturi/avize **de retur**") vine
|
||||
dintr-o citire a comentariului `-- verific daca exista facturi sau avize de retur` in care "de retur"
|
||||
se distribuie si peste "facturi"; codul zice altceva.
|
||||
|
||||
Deci: la ora asta, **exact regula ceruta de decizia 50 este deja implementata pentru avize**
|
||||
(document cu urmasi = blocat; capatul lantului = liber).
|
||||
|
||||
### Ce NU citeste garda
|
||||
|
||||
- **`VANZARI.FACTURAT` nu e citit de nicio garda.** Pe calea de stergere e doar **scris**
|
||||
(`EXPORT:5504-5510` pentru `V_TIP = 24`, `EXPORT:5527-5533` pentru `V_TIP = 4` — resetare la 0
|
||||
cand dispare factura/avizul de retur), iar la facturare e **pus** de `marcheaza_facturat`
|
||||
(`EXPORT:15381-15418`). Niciun `IF` / `RAISE` nu il interogheaza. Semnalul autoritar este
|
||||
`VANZARI_CORESP`, `FACTURAT` e derivat redundant.
|
||||
- **`ID_VANZARE_SURSA` / `ID_VANZARE_DEST` nu exista.** `VANZARI_CORESP` are exact 5 coloane
|
||||
(interogare pe `all_tab_columns`): `ID_VANZARE_CORESP`, `ID_VANZARE_FACT`, `ID_VANZARE_AVIZ`,
|
||||
`STERS`, `TIP`. Nici `VANZARI` nu are coloane `*_SURSA` / `*_DEST` (aceeasi interogare).
|
||||
|
||||
---
|
||||
|
||||
## 2. Exista alte garzi pe calea de stergere / modificare?
|
||||
|
||||
### 2a. Oracle — nu exista alta, dar garda e **atinsa pe ambele ramuri** de stergere
|
||||
|
||||
Interogare pe `all_source` (owner curent): singurele obiecte care contin `VANZARI_CORESP` sunt
|
||||
**`PACK_FACTURARE` (package body)** si **`TRG_VANZARI_CORESP_BEFOINS`**. Deci nu exista o a doua
|
||||
garda ascunsa in alt pachet.
|
||||
|
||||
**Triggerele nu contin garzi** (sursa citita integral din `all_source`):
|
||||
|
||||
| trigger | tabela | eveniment | ce face |
|
||||
|---|---|---|---|
|
||||
| `TRG_VANZARI_BEFOUPD` | `VANZARI` | UPDATE | 4 apeluri `pack_audit.verifica_val` (NR_ACT, SERIE_ACT, DATA_ACT, DATA_SCAD) — audit, nu blocare |
|
||||
| `TRG_VANZARE_BEFOINS` | `VANZARI` | INSERT | — |
|
||||
| `TRG_VANZARI_DET_BEFOINS` | `VANZARI_DETALII` | INSERT | — |
|
||||
| `TRG_VANZARI_CORESP_BEFOINS` | `VANZARI_CORESP` | INSERT | doar `SEQ_VANZARI_CORESP.nextval` |
|
||||
|
||||
**Punct important de verificat, pentru ca la prima citire pare o portita:** in
|
||||
`frm_facturi.do_sterge` apelul direct `pack_facturare.sterge_factura` e **comentat** pe ramura
|
||||
documentelor cu note contabile — `ofacturare_comun.vc2:4807-4809` (`*!* modificare v 2.2.5`), iar
|
||||
ramura activa (`:4810-4811`) cheama numai `pack_contafin.finalizeaza_stergere_nota`. **Nu e o
|
||||
portita**: lantul se inchide in Oracle.
|
||||
|
||||
```
|
||||
PACK_CONTAFIN:8322-8326 SELECT COUNT(*) INTO lnEInVanzari FROM vanzari WHERE cod = tnCod;
|
||||
IF lnEInVanzari > 0 THEN
|
||||
pack_facturare.sterge_din_vanzari(tnCod, tnAn, tnLuna, tnIdUtil);
|
||||
PACK_FACTURARE:14996-14998 SELECT ID_VANZARE INTO V_ID_VANZARE FROM VANZARI WHERE COD = V_COD;
|
||||
pack_facturare.sterge_factura(V_ID_VANZARE, V_LUNA, V_AN, V_ID_UTIL);
|
||||
```
|
||||
|
||||
Apelantii lui `sterge_factura` in baza (cautare pe `all_source`) sunt exact doi:
|
||||
`PACK_FACTURARE.sterge_din_vanzari` (`DB PACK_FACTURARE:14998`) si procedura standalone
|
||||
`STERGE_DOCUMENT` (`DB STERGE_DOCUMENT:416`). Ambele cai trec prin garda.
|
||||
|
||||
### 2b. VFP — pe calea de stergere: nicio garda pe urmasi
|
||||
|
||||
`frm_facturi.do_sterge` (`COMUN\clase\ofacturare_comun.vc2:4661-4877`) verifica, inainte de apel:
|
||||
luna inchisa (`:4676`), `sters = 1` (`:4689`), confirmare (`:4694`), luna curenta (`:4701`) si
|
||||
referinte de incasari/plati (`ReferinteDocumenteNota`, `:4723-4727`). **Nimic despre `FACTURAT`,
|
||||
`VANZARI_CORESP` sau urmasi** — verificarea lantului e delegata integral Oracle-ului.
|
||||
|
||||
### 2c. VFP + Oracle — pe calea de **modificare**: nicio garda, nici pe urmasi, nici pe lant
|
||||
|
||||
`frm_facturi.do_modifica` (`ofacturare_comun.vc2:4541-4640`) blocheaza doar doua lucruri
|
||||
(`:4590`, `:4635`):
|
||||
|
||||
```
|
||||
If poRec.sters = 0 AND NVL(m.pnEFactura,0) = 0
|
||||
...
|
||||
amessagebox("Nu puteti face modificari pe inregistrarile sterse sau facturile trimise in eFactura!", 48, "Atentie")
|
||||
```
|
||||
|
||||
`pack_facturare.modifica_date_factura` (`EXPORT:14439-14509`) e un lant de `UPDATE`-uri fara niciun
|
||||
`IF` de validare si fara niciun `RAISE_APPLICATION_ERROR`. Ceea ce e coerent cu ce modifica azi
|
||||
(ruta, delegat, agent, masina, dataora exp., text aditional, tip SAFT, eFactura, serie/numar/data act,
|
||||
data scadenta) — campuri de antet care nu ating lantul aviz -> factura.
|
||||
|
||||
---
|
||||
|
||||
## 3. Concluzia operationala pentru decizia 50 / S7
|
||||
|
||||
**Regula ceruta de decizia 50 exista deja, si nu are gol pe aviz.** `sterge_factura` implementeaza
|
||||
azi, in trei garzi consecutive, exact "documentul cu urmasi e blocat, capatul lantului e liber":
|
||||
|
||||
| garda | `EXPORT` | ce blocheaza |
|
||||
|---|---|---|
|
||||
| 1 | 5452-5462 | **factura** care are facturi de retur peste ea (`TIP = 3`) |
|
||||
| 2 | 5466-5476 | **aviz** care are factura (`TIP = 1`) sau aviz de retur (`TIP = 2`) peste el |
|
||||
| 3 | 5480-5494 | **factura din aviz** ale carei avize-sursa au primit intre timp aviz de retur |
|
||||
|
||||
A treia garda merita subliniata pentru decizia 50: factura din aviz e editabila **doar** cat timp
|
||||
niciunul dintre avizele ei nu are aviz de retur; nu e "capat de lant" neconditionat.
|
||||
|
||||
**Deci S7 nu are nevoie de o garda noua ca regula — dar are nevoie de o citire noua a aceleiasi
|
||||
conditii, ca moment.** Garda de azi e in interiorul lui `sterge_factura`, adica se manifesta ca
|
||||
`ORA-20000` **in mijlocul** pasului de stergere din regenerare, dupa ce utilizatorul a completat
|
||||
formularul si dupa ce s-a deschis tranzactia. Pentru un flux de editare asta e o esuare tarzie.
|
||||
Ce lipseste e un **pre-flight read-only**, inainte de intrarea in formular, pe exact aceeasi
|
||||
conditie (fara reimplementarea regulii):
|
||||
|
||||
```sql
|
||||
select count(*) from vanzari_coresp
|
||||
where sters = 0 and id_vanzare_aviz = :id_vanzare and tip in (1, 2, 3)
|
||||
```
|
||||
|
||||
plus, pentru cazul "factura din aviz", subinterogarea din garda 3 (`EXPORT:5480-5488`). Garda din
|
||||
`sterge_factura` ramane pe loc ca plasa de siguranta — nu se muta, nu se slabeste.
|
||||
|
||||
Interogarea se face pe `VANZARI_CORESP`, **nu** pe `VANZARI.FACTURAT`: `FACTURAT` e un marcaj
|
||||
redundant, derivat, scris/resetat de `marcheaza_facturat` / `sterge_factura`, si necitit de nicio
|
||||
garda; `VANZARI_CORESP` e semnalul autoritar.
|
||||
|
||||
---
|
||||
|
||||
## 4. Inventar: ce tipuri de documente-parinte produc urmasi prin `VANZARI_CORESP`?
|
||||
|
||||
**Doar trei, toate scrise din acelasi `CASE` din `finalizeaza_factura` (`EXPORT:14818-14839`), prin
|
||||
singurul scriitor `scrie_corespondente_vanzari` (`EXPORT:15481-15516`, singurul `INSERT INTO
|
||||
VANZARI_CORESP` din tot pachetul — si din toata baza, cf. `all_source`).**
|
||||
|
||||
| parinte | urmas | `TIP` | de unde vine lista parintilor |
|
||||
|---|---|---|---|
|
||||
| **aviz** (`VANZARI.TIP in 21, 22, 26, 42`) | factura din aviz (`ntip = 4`) | 1 | `clistaid_avize`, setat la `EXPORT:7037` in `scrie_factura_avize`; lista o construieste VFP prin `caut_avize`, filtrata `a.tip in (21,22,26,42) and a.facturat = 0` — `COMUN\programe\oproceduri_facturare.prg:2036-2038` |
|
||||
| **aviz** | aviz de retur (`ntip = 24`) | 2 | `clistaid` |
|
||||
| **factura** | factura de retur (`ntip in 8, 9`) | 3 | `clistaid` |
|
||||
|
||||
**Ce NU produce urmasi prin `VANZARI_CORESP`** (deci decizia 50 nu are acoperire acolo prin garda
|
||||
existenta):
|
||||
|
||||
- **proforma -> factura.** Proforma sta in `VANZARI` cu `EPROFORMA = 1`; `finalizeaza_factura` nu are
|
||||
ramura care sa scrie corespondenta pentru ea. `TIP = 4` este o **propunere** de la S5c, nu cod
|
||||
existent. Mai mult, `pack_facturare.sterge_proforma` (`EXPORT:5610-5635`) **nu are nicio garda** —
|
||||
corpul ei e doar doua `UPDATE ... SET STERS = 1` pe `VANZARI` si `VANZARI_DETALII`. O proforma din
|
||||
care s-a emis factura se poate sterge azi fara niciun avertisment. (Calea VFP:
|
||||
`ofacturare_comun.vc2:4707-4719`, care iese din `do_sterge` inainte de restul verificarilor.)
|
||||
- **comanda -> factura.** Legatura e `VANZARI.ID_COMANDA` + `pack_facturare.inchide_comanda`
|
||||
(`EXPORT:5769`), nu `VANZARI_CORESP`. `COMENZI` **nu are coloana `FACTURAT`** (verificat pe
|
||||
`all_tab_columns`) — flagul `facturat` din grid vine din view-ul de incarcare. Garda exista, dar e
|
||||
**in VFP si pe comanda**, nu pe factura: `COMUN\clase\ocomenzi.vc2:1806-1807` la modificare
|
||||
("Aceasta comanda a fost facturata si nu mai poate fi modificata!") si `:2065-2066` la stergere.
|
||||
- **contract -> factura.** Legatura e `VANZARI.ID_CTR` + `CTR_RATE_FACTURI`
|
||||
(atinsa de `sterge_factura` la `EXPORT:5566-5580` pentru `V_TIP IN (2, 6, 52)`); nicio linie in
|
||||
`VANZARI_CORESP`, nicio garda de tip "are urmasi".
|
||||
|
||||
---
|
||||
|
||||
## 5. Ce am verificat direct vs. ce am dedus
|
||||
|
||||
**Verificat direct pe cod / pe metadate:**
|
||||
|
||||
- textul celor trei garzi din `sterge_factura`, in export **si** desfasurat din `all_source` (identice);
|
||||
- `CASE`-ul din `finalizeaza_factura` care da semantica lui `TIP`, ramura cu ramura;
|
||||
- corpul lui `scrie_corespondente_vanzari` si `marcheaza_facturat`;
|
||||
- ca `INSERT INTO VANZARI_CORESP` exista intr-un singur loc (grep pe export + `all_source` pe toata baza);
|
||||
- sursa integrala a celor 4 triggere de pe `VANZARI` / `VANZARI_DETALII` / `VANZARI_CORESP`;
|
||||
- lista completa a apelantilor lui `sterge_factura` (`all_source`), inclusiv lantul
|
||||
`finalizeaza_stergere_nota -> sterge_din_vanzari -> sterge_factura`;
|
||||
- coloanele reale ale `VANZARI_CORESP`, `PROFORME`, si absenta lui `FACTURAT` din `COMENZI`
|
||||
(`all_tab_columns`);
|
||||
- `frm_facturi.do_sterge` si `frm_facturi.do_modifica` integral, plus `modifica_date_factura`;
|
||||
- filtrul `caut_avize` din `oproceduri_facturare.prg`.
|
||||
|
||||
**Dedus / cu limite declarate:**
|
||||
|
||||
- Ca `TIP = 1` inseamna "factura normala din aviz" e o deductie din singurul punct de scriere
|
||||
(`ntip = 4` -> `scrie_corespondente_vanzari(1)`) — solida, dar e deductie, nu o eticheta declarata
|
||||
intr-un nomenclator.
|
||||
- Ca setul tipurilor-parinte pentru `TIP = 1` e `{21, 22, 26, 42}` vine din filtrul VFP `caut_avize`,
|
||||
nu dintr-o restrictie in Oracle. Pachetul insereaza **orice** `ID_VANZARE` primit in
|
||||
`clistaid_avize`, fara filtru pe tip (`EXPORT:15494-15504`). Alt apelant (alt produs ROA, un import)
|
||||
ar putea introduce alte tipuri.
|
||||
- Nu am verificat daca vreun **alt produs ROA** (ROACONT / ROAGEST / ...) are o cale proprie de
|
||||
stergere care ocoleste `sterge_factura`. Am verificat doar ca in Oracle nu exista alt apelant si
|
||||
ca in ROAFACTURARE ambele ramuri din `do_sterge` ajung acolo.
|
||||
|
||||
**Din date, deci nedovaditor:** interogarea pe `VANZARI_CORESP` din baza de dev arata `TIP=1` cu
|
||||
parinti de tip 21 si 22, `TIP=2` cu parinte 22, `TIP=3` cu parinte 1 — consistent cu tabelul de mai
|
||||
sus, dar volumul e de ordinul unitatilor (8 randuri in total). **Nu e dovada**; concluziile de mai
|
||||
sus vin din cod.
|
||||
|
||||
## 6. Ce nu s-a putut stabili
|
||||
|
||||
Nimic din cele 4 intrebari nu a ramas nedeterminat. Singurul punct pe care l-as lasa explicit
|
||||
deschis pentru S7 este cel de la §4: **golul real nu e pe aviz, e pe proforma** —
|
||||
`sterge_proforma` nu are absolut nicio garda, iar daca S5c chiar introduce `TIP = 4`
|
||||
(proforma -> factura), garda din `sterge_factura` **nu se aplica automat**, pentru ca `sterge_proforma`
|
||||
e o procedura complet separata care nu o apeleaza.
|
||||
231
docs/cercetare/gol_ntip4_factura_din_avize.md
Normal file
231
docs/cercetare/gol_ntip4_factura_din_avize.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Golul ntip=4 (facturare din avize) fata de proiectarea parametrului de cont contabil
|
||||
|
||||
Cercetare read-only, continua `canal_cont_venit_fara_politica.md` (sectiunea "Proiectarea
|
||||
parametrului de cont contabil", liniile 13-227). Confirma si extinde golul deja semnalat, mai
|
||||
succint, in `parametru_cont_contabilizeaza_articol.md:49-56,167-168`.
|
||||
|
||||
Sursa Oracle: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(17217 linii). **Toate liniile citate mai jos sunt verificate direct pe acest fisier in aceasta
|
||||
runda** — numerele din cererea initiala (7520-7537, 5096) s-au dovedit **identice**, fara offset,
|
||||
fata de fisierul curent (nu +17 cum se anticipa).
|
||||
|
||||
## Verdict, in cinci randuri
|
||||
|
||||
**Ramura `ntip=4` e EXCLUSA STRUCTURAL, dar nu prin mecanismul presupus initial** (nu prin garda
|
||||
`id_pol` din `cursor_articol`/FACT-024 a lui `contabilizeaza_articol`). Blocajul real e **cu o
|
||||
functie mai devreme**: `adauga_articol_factura`, ramura `WHEN pack_facturare.ntip = 4`
|
||||
(`:5080-5103`), cauta randul original din avizul sursa prin `A.ID_POL = V_ID_POL` **fara niciun
|
||||
handler de exceptie** — daca `V_ID_POL` e `NULL` (exact cazul "articol fara politica"), `NULL =
|
||||
NULL` nu se potriveste niciodata in SQL, deci `SELECT ... INTO` **cade mereu cu `NO_DATA_FOUND`
|
||||
neprins**, la momentul **adaugarii articolului in factura** (`adauga_articol_factura`), cu mult
|
||||
inainte ca linia sa ajunga vreodata la `contabilizeaza_articol`. Practic, un articol fara politica
|
||||
**nu poate fi adaugat deloc** pe un document `ntip=4` — eroarea apare la primul pas, nu la scriere.
|
||||
**Gol sora mai important, gasit in aceasta cercetare**: ramurile de **aviz** (`ntip` in afara
|
||||
bucket-ului "factura") — `28`/`29` (SCD hardcodat `461`) si toate celelalte (SCD hardcodat `418`)
|
||||
— **raman perfect accesibile** cu `cont_venit` populat, iar ramura noua propusa hardcodeaza
|
||||
`SCD='4111'` necondiționat de `ntip`, ceea ce ar scrie contul gresit pe orice aviz cu articol fara
|
||||
politica. Vezi sectiunea "Ramuri sora" mai jos — mai grav decat golul `ntip=4` insusi, pentru ca
|
||||
e efectiv atins, nu doar teoretic.
|
||||
|
||||
## 1. Lantul de dovezi pentru "ntip=4 e exclus" — pas cu pas
|
||||
|
||||
### 1.1. `ntip` e o variabila de pachet, setata o singura data per document
|
||||
|
||||
```
|
||||
ff_...:165 ntip NUMBER(2); -- declarata in spec, variabila publica de pachet
|
||||
ff_...:1882 pack_facturare.ntip := V_TIP; -- in initializeaza_date_factura (una din cele 4 supraincarcari, ~1650-1918)
|
||||
```
|
||||
|
||||
`V_TIP` vine din `poDate.Tip` la apelul VFP `pack_facturare.initializeaza_date_factura(...)`
|
||||
(`COMUN\clase\ofacturare.vc2:13981-13999`, argumentul `Alltrim(Str(poDate.Tip))` pozitionat ca
|
||||
`V_TIP`). O data setat, `pack_facturare.ntip` ramane valabil pentru toate apelurile ulterioare
|
||||
din aceeasi tranzactie (`adauga_articol_factura`, apoi `scrie_factura2`/`scrie_factura_avize_retur`/
|
||||
`scrie_aviz_retur` -> `contabilizeaza_articol`) — **e o singura valoare per document, nu per linie**.
|
||||
|
||||
### 1.2. Apelanti VFP care produc `poDate.Tip = 4`
|
||||
|
||||
Comentat consecvent `&& factura din aviz` / `&& aviz` in tot codul VFP:
|
||||
```
|
||||
COMUN\clase\ofacturare_comun.vc2:4347 If poDate.tip = 4 && factura din aviz
|
||||
COMUN\programe\oproceduri_facturare.prg:1253 If poDate.tip = 4 && factura din aviz
|
||||
COMUN\programe\ofacturare.prg:1512 Case poDate.tip = 4
|
||||
COMUN\clase\ofacturare.vc2:9534,9636,9674,14301,15151,18299,19022 Case poDate.tip = 4 (afisare/validare)
|
||||
```
|
||||
`ofacturare.prg:1268,1488,1504` trateaza si combinatia `poDate.tip = 4 And Not
|
||||
Empty(poDate.id_comanda_aviz)` alaturi de `ntip IN (3,21,28,42,47)` — confirma ca `ntip=4` e
|
||||
categoria distincta "facturare pe baza de avize existente" (formularul dedicat de facturare din
|
||||
avize), separata de facturarea directa sau din comenzi.
|
||||
|
||||
### 1.3. `adauga_articol_factura`, ramura `ntip=4` — blocajul real
|
||||
|
||||
```
|
||||
ff_...:4989-5015 PROCEDURE adauga_articol_factura(..., V_ID_POL IN NUMBER, ...)
|
||||
```
|
||||
`V_ID_POL` e parametru direct (nu derivat), trimis de VFP la
|
||||
`COMUN\clase\ofacturare.vc2:14072` (si identic `:18107`):
|
||||
```
|
||||
Nvl(Alltrim(Str(poArt.id_pol)),[NULL])
|
||||
```
|
||||
Daca `poArt.id_pol` e `.NULL.` in VFP (cazul "articol fara politica" — premiza intregului
|
||||
proiect #13), `Str()` propaga `.NULL.`, iar `Nvl(...)` trimite literalul SQL `NULL` — deci
|
||||
`V_ID_POL` ajunge `NULL` in Oracle exact cand articolul n-are politica.
|
||||
|
||||
In corpul `adauga_articol_factura`, CASE-ul care determina pretul/TVA/valuta ramurii (`:5052-5220`)
|
||||
are, pentru `ntip=4`:
|
||||
```
|
||||
ff_...:5080-5103
|
||||
WHEN pack_facturare.ntip = 4 THEN
|
||||
-- facturare din avize
|
||||
SELECT DISTINCT A.PRET, A.PROC_TVAV, A.ID_VALUTA, A.PRET_CU_TVA, B.IN_STOC
|
||||
INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC
|
||||
FROM VANZARI_DETALII A
|
||||
LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL
|
||||
WHERE A.ID_ARTICOL = V_ID_ARTICOL
|
||||
AND A.ID_POL = V_ID_POL
|
||||
AND NVL(A.ID_GESTIUNE, -1000) = V_ID_GESTIUNE
|
||||
AND A.DISCOUNT_UNITAR = V_DISCOUNT_UNITAR
|
||||
AND NVL(A.CONT, 'XXXX') = V_CONT
|
||||
AND A.ID_VANZARE IN (SELECT X FROM table(cast(CHARN2COLLECTION(pack_facturare.clistaid, ',') AS num_tab)));
|
||||
```
|
||||
**Fara niciun `EXCEPTION WHEN NO_DATA_FOUND`** — spre deosebire de ramurile vecine din acelasi
|
||||
CASE: `ntip=45` are handler cu `FACT-018` (`:5140-5145`), `V_OPT_FACTURARE=3` are handler cu
|
||||
fallback + `FACT-012` (`:5167-5185`), `ELSE` are handler cu `FACT-013` (`:5188-5198`). Doar ramurile
|
||||
`ntip IN (3,21,28,42,47)` (`:5053-5078`, cauta in `COMENZI_ELEMENTE` dupa comanda, nu dupa politica)
|
||||
si `ntip=4` sunt fara plasa de siguranta.
|
||||
|
||||
Cu `V_ID_POL = NULL`, `A.ID_POL = V_ID_POL` **nu se poate potrivi niciodata** (semantica SQL: orice
|
||||
comparatie cu `NULL` da `UNKNOWN`, niciodata `TRUE`, indiferent ce e stocat in `A.ID_POL`) —
|
||||
`SELECT ... INTO` (fara `DISTINCT`+agregat care sa tolereze zero randuri) **arunca mereu
|
||||
`NO_DATA_FOUND`**. Neprinsa aici, exceptia se propaga necaptata pana la apelantul PL/SQL (blocul
|
||||
anonim generat de VFP), care se intoarce la `goExecutor` ca eroare Oracle bruta (`ORA-01403: no
|
||||
data found`), **nu ca `FACT-024`** — vezi punctul 3 mai jos pentru diferenta.
|
||||
|
||||
**Concluzie pas 1.3**: un articol fara politica (`V_ID_POL=NULL`, exact scenariul `cont_venit`)
|
||||
**nu poate fi adaugat pe un document `ntip=4`** — apelul `adauga_articol_factura` insusi esueaza,
|
||||
inainte ca `VANZARI_DETALII_TEMP` sa primeasca randul, deci **cu mult inainte** ca
|
||||
`contabilizeaza_articol` (unde e ramura noua `cont_venit`) sa vada vreodata acea linie.
|
||||
|
||||
### 1.4. Blocajul secundar (daca 1.3 n-ar exista): `contabilizeaza_articol` insusi
|
||||
|
||||
Chiar daca ipotetic linia ar ajunge in `VANZARI_DETALII_TEMP` cu `id_pol=NULL`, funcia care scrie
|
||||
efectiv factura are propria garda, **inaintea oricarui ntip**:
|
||||
```
|
||||
ff_...:7278-7302 BEGIN
|
||||
SELECT COMPUS, ID_POL_ART INTO V_COMPUS, V_ID_POL_ART
|
||||
FROM VCRM_POLITICI_PRET_ART
|
||||
WHERE ID_ARTICOL = detalii_articol.id_articol
|
||||
AND ID_POL = detalii_articol.id_pol;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
... RAISE_APPLICATION_ERROR(-20000, '... (FACT-024)');
|
||||
END;
|
||||
```
|
||||
Aceasta ruleaza **la inceputul functiei, inainte de `OPEN cursor_articol`** (`:7393`) si deci
|
||||
inainte de CASE-ul pe `ntip` (`:7398-7423`) si de IF-ul `ntip<>4` (`:7472`). Cu `id_pol=NULL`,
|
||||
`FACT-024` cade aici, indiferent de `ntip` — **confirma independent ca ramura `ntip=4` de la
|
||||
`:7520-7537` (`scrie_fact_aviz_custodie`) e neatinsa azi de o linie fara politica**, e a doua
|
||||
plasa de siguranta, redundanta cu 1.3 dar pe alt strat.
|
||||
|
||||
**Ramura propusa `cont_venit IS NOT NULL`** (proiectare `canal_cont_venit_fara_politica.md`
|
||||
sectiunea 3) se planteaza "inainte de `:7278`" — deci **inlocuieste ambele garda de mai sus** cand
|
||||
`cont_venit` e populat. Asta e sigur (design-ul insusi), dar devine irelevant pentru `ntip=4`
|
||||
pentru ca linia nu ajunge niciodata aici (blocata la 1.3).
|
||||
|
||||
## 2. Ce se intampla defensiv daca cineva ajunge totusi acolo
|
||||
|
||||
Doua scenarii distincte, cu raspunsuri diferite:
|
||||
|
||||
**(a) Scenariul normal — `cont_venit` populat, `id_pol` NULL (cum cere premiza proiectului)**:
|
||||
esec **la adaugarea articolului**, nu la scrierea facturii. Eroare Oracle bruta
|
||||
(`ORA-01403: no data found`), aparuta din `adauga_articol_factura` (`:5080-5103`), propagata prin
|
||||
`goExecutor` catre VFP ca `oPrelucrareEroare()` — **nu `FACT-024`** (acela e alt cod, alta functie,
|
||||
alt moment). Utilizatorul vede un mesaj Oracle generic, nu un mesaj de business explicativ. Nu
|
||||
exista azi niciun cod care sa traduca acest caz intr-un mesaj prietenos — e un gol de UX, nu de
|
||||
integritate a datelor (nu se scrie nimic gresit, doar esueaza fara explicatie buna).
|
||||
|
||||
**(b) Scenariul defensiv real — `cont_venit` SI `id_pol` populate simultan pe aceeasi linie**
|
||||
(nimic in schema nu impune exclusivitate reciproca; `VANZARI_DETALII_TEMP.CONT_VENIT` e o coloana
|
||||
noua, fara `CHECK` care sa lege de `ID_POL`). Daca cineva (bug in VFP, sau folosire deliberata
|
||||
combinata pe viitor) ar trimite ambele populate pe un document `ntip=4`: pasul 1.3 ar reusi
|
||||
(`V_ID_POL` nu mai e NULL, deci `SELECT` din `VANZARI_DETALII` isi gaseste randul), linia intra in
|
||||
`VANZARI_DETALII_TEMP` cu `cont_venit` **si** `id_pol` completate. La `contabilizeaza_articol`,
|
||||
ramura noua (`detalii_articol.cont_venit IS NOT NULL`) preia controlul complet — **fara sa
|
||||
verifice `ntip`** — si executa necondiționat `scrie_nota` + `descarca_gestiune` + discount, exact
|
||||
ca pentru o factura normala. **Nu cade cu nicio eroare. Scrie tacut date gresite**: sare complet
|
||||
peste `scrie_fact_aviz_custodie` (`:7521-7536`), care exista tocmai pentru ca marfa a iesit deja
|
||||
din gestiune cand s-a scris avizul sursa — rularea `descarca_gestiune` in loc de acea functie
|
||||
**descarca stocul a doua oara** pentru aceeasi marfa, fara nicio garda care sa prinda dubla
|
||||
scadere. Acesta e raspunsul relevant pentru textul de plan: nu un crash, un **defect tacut de
|
||||
date** daca vreodata cele doua canale se intalnesc pe aceeasi linie.
|
||||
|
||||
## 3. Toate valorile `ntip` relevante in zona `contabilizeaza_articol` (`:7173-7547`) — verdict per valoare
|
||||
|
||||
Ramura noua (`cont_venit IS NOT NULL`) inlocuieste tot ce e listat mai jos, necondiționat de `ntip`
|
||||
(design-ul, sectiunea 3, nu mentioneaza `ntip` deloc in continutul ramurii noi). Verdictul de mai
|
||||
jos e "atinsa" = poate ajunge cu `cont_venit` populat prin `adauga_articol_factura` fara sa esueze
|
||||
la pasul 1.3-echivalent; "neatinsa" = blocata structural inainte de a ajunge aici (ca `ntip=4`);
|
||||
"ambigua" = depinde de alt cod neverificat complet in aceasta runda.
|
||||
|
||||
| `ntip` | Comportament azi in `contabilizeaza_articol` | Atinsa de linie `cont_venit`? | Verdict fata de ramura noua |
|
||||
|---|---|---|---|
|
||||
| `<=20` sau `IN (43,44,45,46,48,49,51,52)` (`:7400-7412`) | SCD/ASCD din politica (`crs_rand_articol.scd`) — bucket "factura" | **DA** — `adauga_articol_factura` pentru aceste `ntip` foloseste calea generica (`ELSE`, `:5187-5203`, fara cerinta de `id_pol`) sau ramuri proprii (`2,6,26,52`→contract, `45`→restaurant) care oricum accepta `V_PRET_TEMP` cand nu gasesc potrivire | **E cazul acoperit de design** — SCD='4111' hardcodat e gandit exact pentru acest bucket. Fara probleme noi, cu exceptia sub-cazurilor de mai jos. |
|
||||
| `= 46` (`nTipNotaPlata`), sub-caz al bucketului "factura" | `scrie_nota` **NU se apeleaza** (`:7441`, `IF ntip <> nTipNotaPlata`) | DA (e in bucketul de mai sus) | **GOL SORA nou, neconsemnat in proiectare**: ramura noua apeleaza `scrie_nota` necondiționat — pentru un document `NotaPlata` cu linie `cont_venit`, s-ar scrie o nota care azi e sarita intentionat. De adaugat aceeasi garda in ramura noua. |
|
||||
| `= 7`, sub-caz al bucketului "factura" | `EXPLICATIE` primeste sufix `' INVOICE:' + cdescriere` (`:7431-7433`) | DA | **Gol cosmetic**: ramura noua foloseste direct `detalii_articol.explicatia`, fara sufix. Probabil acceptabil (design-ul il numeste "mai bun"), dar e o schimbare de continut pe documente de export, de confirmat. |
|
||||
| `IN (8,9)`, sub-caz al bucketului "factura" | `EXPLICATIE` primeste sufix `' RETUR FACTURA:' + cdescriere` (`:7434-7436`) | DA | Acelasi gol cosmetic ca mai sus. |
|
||||
| `IN (28,29)` (`:7413-7417`) | SCD hardcodat `'461'` — aviz catre clienti debitori | **DA** — `adauga_articol_factura` pentru `28` intra pe ramura `ntip IN (3,21,28,42,47)` (`:5053-5078`), care cauta in `COMENZI_ELEMENTE` dupa comanda+pret, **nu dupa `id_pol`** — un articol fara politica poate gasi potrivire aici daca exista element de comanda corespunzator | **GOL SORA, mai grav decat `ntip=4`**: ramura noua ar scrie `SCD='4111'` in loc de `'461'` pe un aviz catre client debitor — cont contabil gresit, atins efectiv. |
|
||||
| toate celelalte (`ELSE`, `:7418-7422`: `21,22,23,24,25,27,30,41,42,47,...`) | SCD hardcodat `'418'` — aviz generic | **DA, pentru aceleasi motive** (multe din aceste `ntip` — `3,21,42,47` — trec prin ramura "din comenzi" fara cerinta de `id_pol`; altele avansate direct din bucket implicit `ELSE` din `adauga_articol_factura`, `:5187-5203`, care nu cere `id_pol` deloc) | **Acelasi gol SCD hardcodat**, aplicabil la orice document de tip aviz (nu doar 28/29). |
|
||||
| `= 4` (`:7472,7520-7537`) | `scrie_fact_aviz_custodie` in loc de `descarca_gestiune`+discount | **NU** — exclus structural la pasul 1.3 (`adauga_articol_factura` esueaza inainte) | Neatins in scenariul normal (a); atins doar in scenariul defensiv (b) daca cineva forteaza `id_pol` si `cont_venit` simultan — vezi sectiunea 2. |
|
||||
|
||||
**Cel mai important rezultat al acestui tabel**: golul cerut explicit (`ntip=4`) e cel **mai putin
|
||||
grav** dintre cele gasite, pentru ca e singurul exclus structural. Golurile reale, efectiv atinse,
|
||||
sunt hardcodarea `SCD='4111'` pe orice document de tip **aviz** (`28`,`29` si bucket-ul `ELSE`) si
|
||||
omisiunea gardei `ntip=46` pentru `scrie_nota`.
|
||||
|
||||
## 4. Text propus pentru plan (sectiunea deciziei 34)
|
||||
|
||||
```
|
||||
**Ramura `ntip=4` (facturare din avize, `:7520-7537`) — exclusa structural, nu necesita cod
|
||||
suplimentar.** `adauga_articol_factura`, ramura `WHEN ntip=4` (`:5080-5103`), cauta randul sursa
|
||||
din `VANZARI_DETALII` prin `A.ID_POL = V_ID_POL`, fara handler de exceptie — cu `V_ID_POL=NULL`
|
||||
(exact cazul liniei fara politica), comparatia nu se poate potrivi niciodata, deci apelul cade cu
|
||||
`ORA-01403` la adaugarea articolului, inainte ca linia sa ajunga la `contabilizeaza_articol`. Un
|
||||
articol fara politica nu poate fi, structural, adaugat pe un document `ntip=4`; ramura noua
|
||||
`cont_venit` nu are nimic de tratat aici.
|
||||
|
||||
**Gol descoperit, de acoperit inainte de implementare**: ramurile de **aviz** (`ntip IN (28,29)` →
|
||||
`SCD='461'`, celelalte → `SCD='418'`, `:7413-7422`) **raman accesibile** cu `cont_venit` populat —
|
||||
`adauga_articol_factura` nu cere `id_pol` pentru aceste `ntip` (foloseste calea "din comenzi" sau
|
||||
calea implicita). Ramura noua trebuie sa aleaga `SCD` in functie de `ntip` (pastrand `'461'`/`'418'`
|
||||
pentru avize), nu sa hardcodeze `'4111'` necondiționat — altfel orice aviz cu articol fara politica
|
||||
primeste contul de clienti in loc de contul de aviz. Similar, ramura noua trebuie sa pastreze garda
|
||||
`IF ntip <> nTipNotaPlata` inainte de a apela `scrie_nota` (azi sarita pentru `ntip=46`).
|
||||
|
||||
**Defensiv (opțional)**: daca se doreste o garda explicita in loc de a te baza pe imposibilitatea
|
||||
structurala de mai sus, se poate adauga `IF detalii_articol.cont_venit IS NOT NULL AND
|
||||
pack_facturare.ntip = 4 THEN RAISE_APPLICATION_ERROR(...)` la inceputul ramurii noi — util doar
|
||||
daca cineva ar reusi vreodata sa populeze simultan `id_pol` si `cont_venit` pe o linie `ntip=4`
|
||||
(azi imposibil pe caile VFP cunoscute), caz in care ramura noua ar rula altfel tacut
|
||||
`descarca_gestiune` in loc de `scrie_fact_aviz_custodie`, riscand dubla descarcare de gestiune.
|
||||
```
|
||||
|
||||
## 5. Ce nu s-a putut stabili in aceasta runda
|
||||
|
||||
- **Daca exista vreo cale VFP care sa populeze simultan `poArt.id_pol` si viitorul `cont_venit`**
|
||||
(scenariul defensiv (b)) — cercetarea de fata arata doar ca schema/codul Oracle nu o impiedica;
|
||||
n-am gasit (si nici n-am cautat exhaustiv) un loc in VFP care ar genera aceasta combinatie azi.
|
||||
Probabil imposibil pe fluxurile curente (proiectarea de baza presupune `cont_venit` populat
|
||||
**doar** cand `id_pol` lipseste), dar nu demonstrat exhaustiv pe tot codul VFP.
|
||||
- **Comportamentul exact `oExecute`/`oPrelucrareEroare` la o exceptie Oracle neprinsa** (scenariul
|
||||
(a)) — presupus ca ajunge la utilizator ca mesaj Oracle brut prin mecanismul general de eroare al
|
||||
`goExecutor`, dar nu am urmarit codul `oPrelucrareEroare()` in aceasta runda pentru a confirma
|
||||
formatarea exacta.
|
||||
- **Daca `ntip IN (23,25,30,41)` (transfer subunitati) si `IN (42,47)` (custodie) pot, in practica,
|
||||
sa primeasca un articol fara politica** prin ramura "din comenzi" a `adauga_articol_factura`
|
||||
(`:5053-5078`) — argumentat structural posibil (nu cere `id_pol`), dar nu testat pe date, nu
|
||||
urmarit pana la capat daca `COMENZI_ELEMENTE` insusi ar putea contine un rand fara politica.
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare incheiata, toate cele 5 livrabile cerute de team-lead sunt in sectiunile 1-4 de mai sus.
|
||||
Niciun cod atins, nicio scriere Oracle, doar `SELECT`/`Read`/`Grep`. Context consumat moderat —
|
||||
nu a fost necesara predarea de context.
|
||||
191
docs/cercetare/handoff_s4_runda1.md
Normal file
191
docs/cercetare/handoff_s4_runda1.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Handoff — S4 runda 1 (PAGE3 doar afisare), predare pe prag de context
|
||||
|
||||
Sesiune intrerupta la prag de context (`monitorizare-context.md`). Stare **stabila si consistenta**
|
||||
(text = binar), dar cu schele de diagnostic inca in cod — de scos inainte de livrare.
|
||||
|
||||
## 1. Ce am modificat, unde, si write-back-ul
|
||||
|
||||
Toate in `COMUN` (cross-proiect).
|
||||
|
||||
- **`COMUN\programe\ofacturare_editare.prg`** — **write-back N/A (e `.prg`, nu `.vcx`, se ruleaza direct)**.
|
||||
Antet actualizat (linia cu `*!* helpere...`) + doua functii noi, adaugate la coada fisierului:
|
||||
- `IncarcaVanzareNota(tnCod, tnNract, tcSerieAct, tdDataAct)` — gaseste randul din `VANZARI`
|
||||
corespunzator notei curente, in cursorul `tvanz`.
|
||||
- `IncarcaArticoleFactura(tnIdVanzare)` — incarca liniile active din `VANZARI_DETALII` (+ denumire
|
||||
articol/gestiune/valuta) in cursorul `tvd`.
|
||||
Fisier ASCII (fara diacritice), editat direct cu Edit tool — fara riscul de corupere cp1252.
|
||||
|
||||
- **`COMUN\clase\omodificari.vc2`** (clasa `frm_modific2024`) — **text si binar SINCRONIZATE**,
|
||||
ambele includ inca **cod de diagnostic care trebuie scos**:
|
||||
- PAGE3 nou pe `pgfArticole` (`PageCount=3`, `PAGE3.Caption="Articole factura"`) — linia cu
|
||||
`PageCount = 3, ;` in blocul `ADD OBJECT 'pgfArticole'`.
|
||||
- Grid nou `pgfArticole.PAGE3.grdArticoleFactura` (13 coloane, `RecordSource="tvd"`,
|
||||
`ReadOnly=.T.` la nivel de grid si pe fiecare `Text1`), plus intrarile `OBJECTDATA`
|
||||
corespunzatoare (cautabile dupa `grdArticoleFactura`).
|
||||
- Proprietati noi pe clasa: `lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare` (in blocul
|
||||
`*<PropValue>`, cautabile dupa `lareaarticolevanzari`/`nidvanzare`/`ntipvanzare`).
|
||||
- `Load()`: placeholder `CREATE CURSOR tvd (...)` daca nu e deja deschis — **necesar**, altfel
|
||||
grid-ul incearca `USE tvd` la constructie si pica pe dialog nativ "Open" (vezi sectiunea 4).
|
||||
- `Show()`: bloc nou (cautabil dupa `This.lAreArticoleVanzari`) care apeleaza
|
||||
`IncarcaVanzareNota`/`IncarcaArticoleFactura` si comuta `pgfArticole.PageCount` intre 2 si 3.
|
||||
- **DE SCOS inainte de livrare**: 5 linii `STRTOFILE(...'diag_class.txt'...)` inserate ca
|
||||
checkpoint-uri de depanare in `Init()` (3 linii: inceput, dupa `DoDefault()`, sfarsit) si
|
||||
`Load()` (2 linii: inceput, sfarsit) — cautabile dupa `diag_class`. Nu au efect functional
|
||||
(doar scriu intr-un fisier de log), dar nu trebuie sa ramana in cod livrat.
|
||||
- **Fidelity check txt2vcx: OK** la ultimul write-back (cel CU liniile de diagnostic inca in el).
|
||||
`.vcx`/`.vct` au mtime 12:10, exact dupa acel write-back — deci **binarul reflecta exact
|
||||
`.vc2`-ul curent**, inclusiv diagnosticul.
|
||||
|
||||
### Backup-uri pe disc (`COMUN\clase\`)
|
||||
|
||||
| Fisier | Continut |
|
||||
|---|---|
|
||||
| `omodificari.vc2.pre_s4runda1.bak` | Originalul, dinaintea oricarei modificari S4 |
|
||||
| `omodificari.vc2.pre_diag.bak` | Versiunea mea **curata** (fara cele 5 linii de diagnostic) — **asta e sursa de folosit pentru versiunea finala** |
|
||||
| `omodificari.vc2.mine_s4_full.bak` | Identic cu `pre_diag.bak` (copie facuta in timpul unui test de izolare) |
|
||||
| `omodificari.vcx.mine_s4.bak` / `omodificari.vct.mine_s4.bak` | **Binarul** corespunzator lui `pre_diag.bak`, copiat direct (fara compilare) — restaurare instantanee |
|
||||
|
||||
**Pasul urmator recomandat**: `Copy-Item omodificari.vc2.pre_diag.bak -> omodificari.vc2`,
|
||||
`Copy-Item omodificari.vcx.mine_s4.bak -> omodificari.vcx` (+ `.vct`) — restaurare **instantanee**
|
||||
(fara `txt2vcx`) la versiunea curata cu PAGE3 functional, fara schela de diagnostic. Verifica dupa
|
||||
aceea cu un `diff` ca `omodificari.vc2` chiar nu mai contine `diag_class`.
|
||||
|
||||
## 2. Ce mai ramane din runda 1
|
||||
|
||||
Din sectiunea E / runda 1 a planului (A.2 PageCount + A.3 detectie + B.1 incarcare + B.2 marcaje):
|
||||
|
||||
- **A.2 (PageCount)** — FACUT, testat static (fidelity OK), **netestat live pe formular** (vezi
|
||||
sectiunea 3 — blocaj de mediu, nu s-a ajuns sa vad `PageCount` pe formularul instantiat real).
|
||||
- **A.3 (detectie)** — FACUT, testat **direct** (fara UI) cu rezultate PASS pe date reale (sectiunea
|
||||
3). Deviaza de la recomandarea planului (cauta si de ce, sectiunea 4).
|
||||
- **B.1 (incarcare cursoare)** — FACUT, testat direct, PASS.
|
||||
- **B.2 (marcaje stare linii)** — **NU e in scope runda 1** (grid-ul e strict readonly, fara
|
||||
editare — marcajele `_modificat`/`sters`/`id_vanzare_det=0` sunt pentru runda 2). Nu e inceput.
|
||||
|
||||
**Ce lipseste efectiv**: verificarea end-to-end ca, pe formularul REAL (`Createobject('frm_modific2024')`
|
||||
+ `.Show()`), `PageCount` chiar ajunge 3 pe o factura si 2 pe o nota fara `VANZARI` — blocata de
|
||||
problema de mediu din sectiunea 3. Codul e scris si compilat curat; ramane doar confirmarea live.
|
||||
|
||||
## 3. Ce am testat, cu ce rezultat
|
||||
|
||||
Suita: `COMUN\utile\Teste\editare_factura\test_page3_articole.prg` (headless, `vfp9.exe -A -T`,
|
||||
conexiune reala `MARIUSM_AUTO`). Log: `test_page3_articole_log.txt` in acelasi folder.
|
||||
|
||||
**PASS, pe date reale, testat direct (apel functie, fara formular)**:
|
||||
- `cod=1140888` → `IncarcaVanzareNota` gaseste `id_vanzare=1050`, `tip=1`; `IncarcaArticoleFactura`
|
||||
incarca 4 linii — coincide exact cu baza de regresie din `progres.md` (`TOTAL_CU_TVA=1924.59`).
|
||||
- `cod=1140885` → gaseste `id_vanzare=1047`, `tip=-12` (nu e factura, e alt tip de document) —
|
||||
confirma ca detectia functioneaza pe **orice** tip din `VANZARI`, nu doar facturi (decizia 19).
|
||||
- `cod=1125486` (an=2008, luna=2, notă contabila fara nicio legatura cu `VANZARI`) → 0 randuri
|
||||
gasite — cazul negativ cerut de criteriul de "gata" al rundei.
|
||||
- **Coliziune pe `cod=1139934`** (patru randuri active in `VANZARI`, acelasi cod): filtrul compus
|
||||
(cod+nract+serie_act+data_act) gaseste EXACT randul cerut (`nract=375/SSS` → `id_vanzare=882`) si
|
||||
NU gaseste nimic pentru un `nract` fara corespondent (`nract=13`) — vezi sectiunea 4, e important.
|
||||
|
||||
**NETESTAT live (formular real)**: `verifica_pagecount_form` (in acelasi fisier de test) instantiaza
|
||||
`frm_modific2024` exact ca `do_editare_factura` (`Createobject`+`Show`) si verifica `PageCount` —
|
||||
**se blocheaza intr-un dialog nativ VFP "View Parameter"** in timpul `Createobject`, inainte sa
|
||||
apuce sa ruleze vreo linie din `Show()`. Vezi sectiunea 4 pentru diagnosticul facut si o pista noua,
|
||||
neexplorata inca, primita de la team-lead.
|
||||
|
||||
**`test_baseline_isolation.prg`** (in acelasi folder) — **fisier temporar de diagnostic, se poate
|
||||
sterge** dupa ce cineva confirma diagnosticul de mai jos independent; nu face parte din suita
|
||||
finala (antetul lui o spune explicit). L-am folosit ca sa demonstrez ca **binarul ORIGINAL (dinainte
|
||||
de S4) NU are acest blocaj** in acelasi mediu de test (`PageCount=2`, `done`, fara dialog) — deci
|
||||
problema e din diff-ul meu, nu un gol preexistent de mediu.
|
||||
|
||||
## 4. Descoperiri care nu trebuie pierdute
|
||||
|
||||
### `VANZARI.COD` NU e unic — dovedit pe date, nu presupus
|
||||
|
||||
Contrar recomandarii A.3 din plan ("`SELECT ... FROM vanzari WHERE cod = tact.cod`"), am verificat
|
||||
direct pe `MARIUSM_AUTO`:
|
||||
- Indexul `IDX_VANZARI_04` e pe `(STERS, COD)` si e **NONUNIQUE** — schema insasi nu garanteaza
|
||||
unicitate.
|
||||
- `cod=1139934` are **4 randuri active** (`STERS=0`) in `VANZARI`, cu `TIP`/`NUMAR_ACT` diferite
|
||||
(375/SSS, 24/PF, 25/PF, 26/PF). `cod=1139555` are 2 randuri cu `TIP` diferit (43 si 1).
|
||||
- Filtrarea suplimentara pe `NUMAR_ACT`+`SERIE_ACT`+`DATA_ACT` (toate disponibile pe cursorul `tact`
|
||||
ca `nract`/`serie_act`/`dataact`, din `vact_tot`) rezolva ambiguitatea: `(COD, NUMAR_ACT,
|
||||
SERIE_ACT, DATA_ACT)` verificat **fara nicio dublura** pe toata tabela `VANZARI`.
|
||||
- `ID_FACT` (alternativa mentionata in `progres.md` — "toate legaturile trec prin ID_FACT sau
|
||||
ID_VANZARE, niciodata prin cod") **nu e utilizabil ca filtru unic aici**: e `NULL` pe o fractie
|
||||
mare de randuri chiar si pentru facturi normale (`tip=1`: 58 din 183 `NULL`; `tip=51`: 58 din 74
|
||||
`NULL`), deci n-ar gasi avizele/tipurile fara facturare clasica — ar incalca decizia 19.
|
||||
|
||||
**Concluzie aplicata in cod**: `IncarcaVanzareNota` filtreaza pe cele 4 coloane compuse, nu doar pe
|
||||
`cod`. Aceasta e o **corectie de fond fata de A.3 din plan**, nu o improvizatie — sectiunea C.1 din
|
||||
`plan_06_s4_proiectare.md` ar trebui sa mentioneze asta daca cineva o rescrie.
|
||||
|
||||
### Blocaj "View Parameter" la `Createobject('frm_modific2024')` — cauza NECONFIRMATA
|
||||
|
||||
Simptom: procesul `vfp9.exe` ramane blocat (CPU~0, Responding=True) intr-un dialog nativ **"View
|
||||
Parameter"** exact in timpul liniei `Createobject([frm_modific2024], lnIdSet)`, inainte sa ajunga
|
||||
la orice linie din `Show()` — confirmat cu checkpoint-uri `STRTOFILE` in `Init()`/`Load()` (niciunul
|
||||
nu s-a scris, deci blocajul e chiar mai devreme decat `Load()`, sau checkpoint-urile nu s-au atins
|
||||
din alt motiv neexplicat).
|
||||
|
||||
**Exclus cu dovezi** (nu pierde timp re-verificand):
|
||||
- **Nu e cursorul `tvd` lipsa** — am incercat atat placeholder in `Load()`, cat si pre-creare in
|
||||
scriptul de test inainte de `Createobject`; blocajul a persistat identic.
|
||||
- **Nu e un gol de mediu preexistent** — binarul ORIGINAL (`omodificari.vcx.mine_s4.bak` restaurat
|
||||
temporar) ruleaza curat in ACELASI mediu de test (`test_baseline_isolation.prg`, `PageCount=2`,
|
||||
fara dialog). Deci e ceva din diff-ul meu.
|
||||
- **Un blocaj anterior, diferit** (`File 'crsjtvatemp.dbf' does not exist`, dialog "Open") era
|
||||
intr-adevar preexistent — cerut de `Column63` din `grdRulaje` (PAGE1, cod netusat de mine),
|
||||
rezolvat in scriptul de test cu `update_jtva_coloane("", "crsJtvaTemp", 0)` inainte de
|
||||
`Createobject` (nu in clasa — e o lipsa de mediu de test, nu de productie: in productie,
|
||||
`crsJtvaTemp` e populat pe alte cai inainte sa ajunga un utilizator la acest formular).
|
||||
- Am citit `_pageframe.Init()` (`_baza.vc2:514-523`, itereaza `For i = 1 To PageCount` si cheama
|
||||
`tradu(.Caption)`) ca prim suspect — **`tradu()` (`oproceduri_comune.prg:1746`) e confirmat
|
||||
inofensiv** (doar `STRTRAN` pe diacritice, fara Oracle/View). Nu e cauza, dar ramane singurul cod
|
||||
DEPENDENT de `PageCount` gasit prin cautare in `_baza.vc2`/`_frm_base.vc2`/`_grd_base.vc2`.
|
||||
- Am cautat "CREATE SQL VIEW" / `USE ... VIA` in toata ierarhia de clase (`omodificari`, `_baza`,
|
||||
`_frm_base`, `_grd_base`, `gridextras`) — **zero rezultate**. Niciun view local gasit static.
|
||||
|
||||
**Pista noua, neexplorata** (primita de la team-lead, nu verificata de mine inca): dialogul "View
|
||||
Parameter" apare tipic cand o variabila referita prin `?variabila` (SQL passthrough) sau intr-un
|
||||
view parametrizat e declarata `LOCAL` in loc de `PRIVATE` intr-un harness de test — `LOCAL` nu e
|
||||
vizibil in rutinele apelate. Exemplu citat: `do_editare_factura` declara
|
||||
`Private pnAn, pnLuna, lnCod, lnIdFact, lnIdVanzare` (`ofacturare_comun.vc2:3742`). **Nu am apucat
|
||||
sa verific** daca `test_page3_articole.prg` foloseste `LOCAL` unde codul original ar cere `PRIVATE`
|
||||
undeva in lantul `IncarcaCursoareModificareNota`/`Createobject` — e primul lucru de incercat inainte
|
||||
de a relua sapatul cu `EnumWindows`/`PrintWindow` (costisitor, ~15 cicluri de test in aceasta
|
||||
sesiune doar pentru asta).
|
||||
|
||||
## 5. Capcane de mediu platite in aceasta sesiune
|
||||
|
||||
- **Sterge intotdeauna `.fxp`-ul vechi inainte de fiecare rulare de test** — m-a pacalit o data:
|
||||
am editat `.prg`-ul dar am uitat sa sterg `.fxp`, iar VFP a rulat tacut codul VECHI compilat,
|
||||
dand rezultate inconsistente intre rulari identice in aparenta. Simptom: log-ul arata alt
|
||||
comportament decat sursa curenta ar trebui sa produca.
|
||||
- **FoxBin2Prg NU pastreaza ordinea textuala la scriere-citire (roundtrip)** pentru ADD OBJECT
|
||||
multiple si proprietati custom — le REGENEREAZA in ordine proprie (alfabetica pentru
|
||||
coloane/obiecte fii, alta regula neclara pentru proprietati custom simple — `nidvanzare` a iesit
|
||||
INAINTEA lui `nid_set`, desi `_` < `v` in ASCII). Fidelity-check-ul compara text-sursa cu
|
||||
text-regenerat, deci **orice ordine "naturala" (alfabetica presupusa de mine) poate pica fidelity**.
|
||||
Solutie aplicata: dupa primul fidelity FAIL, am adoptat ca sursa noua textul regenerat din
|
||||
`<staging>\verify\*.vc2` (e deja in forma canonica), nu am incercat sa ghicesc ordinea corecta.
|
||||
Confirma exact indicatia din `flux-editare-vfp-text.md`.
|
||||
- **`InputMask` cu literal (nu `get_mask(...)`)** trebuie **ghilimeluit** (`"9.99"`), nu bar (`9.99`)
|
||||
— altfel VFP il interpreteaza numeric, nu ca string de format. Prins abia la inspectia manuala a
|
||||
textului regenerat, fidelity-check-ul NU l-a semnalat separat (a picat impreuna cu reordonarea).
|
||||
- **Grid nou cu `RecordSource` pe un cursor care nu exista inca la constructia formularului** →
|
||||
VFP incearca `USE <recordsource>` ca fisier fizic si arata dialogul nativ "Open" daca nu-l
|
||||
gaseste pe disc — exact acelasi tipar deja documentat in clasa pentru `saft_taxtable`/
|
||||
`saft_mecanisme_plati` (`Load()`, verificat `If !Used(...)` inainte de orice altceva). Solutia
|
||||
e aceeasi: placeholder gol creat in `Load()`, INAINTE ca framework-ul sa construiasca grid-urile.
|
||||
- **Query-uri Oracle multiple rulate manual (sqlplus) au fost esentiale** pentru validarea empirica
|
||||
a filtrului compus pe `VANZARI` — nu s-ar fi descoperit coliziunea pe `cod` doar din cod static.
|
||||
|
||||
## 6. Ce recomand pentru urmatorul agent
|
||||
|
||||
1. Restaureaza versiunea curata (sectiunea 1, pasul recomandat) — 2 copieri de fisier, fara
|
||||
compilare, verifica cu `grep diag_class` ca a disparut.
|
||||
2. Incearca pista `LOCAL`/`PRIVATE` din sectiunea 4 pe `test_page3_articole.prg` inainte de orice
|
||||
alta depanare GUI.
|
||||
3. Daca se rezolva: reruleaza `test_page3_articole.prg` complet, confirma PASS pe toate cazurile
|
||||
(inclusiv `verifica_pagecount_form`), sterge `test_baseline_isolation.prg` + `.fxp` + log-ul lui,
|
||||
scrie diff-ul (diff aplicat (sters), `git diff --no-index <bak> <editat>`) si
|
||||
raportul (`docs\cercetare\rec_s4_runda1.md`) cerute initial de team-lead.
|
||||
4. Daca team-lead sau alt agent scrie capcana "View Parameter / LOCAL vs PRIVATE" in
|
||||
`testare-ui-vfp.md`, nu o duplica — team-lead a intrebat explicit cine o scrie.
|
||||
337
docs/cercetare/idfact_refolosire_si_documente.md
Normal file
337
docs/cercetare/idfact_refolosire_si_documente.md
Normal file
@@ -0,0 +1,337 @@
|
||||
Cercetare S9: reemiterea unui document cu acelasi ID_FACT (SET_IDFACT + DOCUMENTE)
|
||||
====================================================================================
|
||||
|
||||
Verdict (rezumat)
|
||||
------------------
|
||||
**DA, dar cu conditii — nu e o schimbare izolata.** `SET_IDFACT` activ (`PACK_CONTAFIN.pck:3037-3040`)
|
||||
ia neconditionat un `ID_FACT` nou din `SEQ_IdFact`; niciun apelant din toata suita (~25-30 copii
|
||||
identice ale `oscrie_in_fisiere.prg`, cate una per produs) ii impune un `ID_FACT`. Varianta comentata
|
||||
(`:3016-3035`, cauta pe `NRACT+SERIE_ACT+DATAACT+ID_CTR` cu `STERS=0`) **nu rezolva S9** ca atare:
|
||||
dupa stergerea soft a documentului vechi, `STERS` e deja 1, cautarea nu-l gaseste, si cade tot pe
|
||||
secventa — plus riscul de coliziune cu un document viitor neinrudit care are aceleasi 4 campuri.
|
||||
Blocajul real nu e in `SET_IDFACT`, ci in scrierea din `DOCUMENTE`: acolo e un `INSERT` simplu
|
||||
(`:796-817`) pe `ID_DOC`, care e **PRIMARY KEY** (`PK_DOCUMENTE`, unic, `fn_script.sql:5192-5198`).
|
||||
Stergerea (`STERGE_DIN_ACT:1835-1855`) e soft-delete pur — `ACT.STERS=1`, apoi `DOCUMENTE.STERS=1`
|
||||
pentru id_fact-urile gasite pe acel `COD` — randul vechi **nu se sterge fizic**. Deci reemiterea cu
|
||||
acelasi `ID_FACT`, cu codul de azi neschimbat, ar arunca `ORA-00001` la insertul in `DOCUMENTE`,
|
||||
pentru ca randul cu acel `ID_DOC` inca exista (doar marcat `STERS=1`). Varianta `MERGE` deja
|
||||
comentata (`:818-847`) nu ajuta din prima: are doar `WHEN NOT MATCHED`, fara `WHEN MATCHED`, deci
|
||||
daca randul exista deja l-ar ignora tacit — documentul reemis ar ramane cu `DOCUMENTE.STERS=1`.
|
||||
Concluzie operationala: reutilizarea e posibila, dar cere trei schimbari simultane (detaliate la C.7),
|
||||
nu doar reactivarea codului comentat din `SET_IDFACT`.
|
||||
|
||||
A. SET_IDFACT
|
||||
--------------
|
||||
|
||||
### A.1 — Ramura activa vs. ramura comentata
|
||||
|
||||
Cod integral, `PACK_CONTAFIN.pck:3014-3040`:
|
||||
|
||||
```
|
||||
------------------------------------------------------------------------------------
|
||||
/* -- 25.02.2013 : am comentat pentru ca se face unirea id_fact pe listarea din JC/JV
|
||||
PROCEDURE SET_IDFACT(tdDataAct ACT_TEMP.DATAACT%TYPE,
|
||||
tcSerie_Act ACT_TEMP.SERIE_ACT%TYPE,
|
||||
tnNrAct ACT_TEMP.NRACT%TYPE,
|
||||
tnId_Ctr ACT_TEMP.ID_CTR%TYPE) IS
|
||||
V_ID_FACT DOCUMENTE.ID_DOC%TYPE;
|
||||
BEGIN
|
||||
BEGIN
|
||||
SELECT ID_DOC
|
||||
into pack_contafin.nIdFact
|
||||
FROM DOCUMENTE
|
||||
WHERE NRACT = tnNrAct
|
||||
AND NVL(SERIE_ACT, '+-') = NVL(tcSerie_Act, '+-')
|
||||
AND DATAACT = tdDataAct
|
||||
AND NVL(ID_CTR, 0) = NVL(tnId_Ctr, 0)
|
||||
AND STERS = 0;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
SELECT SEQ_IdFact.NEXTVAL into pack_contafin.nIdFact FROM DUAL;
|
||||
END;
|
||||
END SET_IDFACT;*/
|
||||
------------------------------------------------------------------------------------
|
||||
PROCEDURE SET_IDFACT(V_GCS IN VARCHAR2) IS
|
||||
BEGIN
|
||||
SELECT SEQ_IdFact.NEXTVAL into pack_contafin.nIdFact FROM DUAL;
|
||||
END SET_IDFACT;
|
||||
```
|
||||
|
||||
Diferenta de comportament daca s-ar reactiva varianta comentata: in loc sa genereze mereu un
|
||||
`ID_FACT` nou, ar cauta intai in `DOCUMENTE` un document **nesters** (`STERS=0`) cu aceeasi
|
||||
combinatie `NRACT + SERIE_ACT + DATAACT + ID_CTR`, si doar daca nu gaseste nimic ar cere secventa.
|
||||
Doua probleme pentru S9:
|
||||
- **nu se aplica la reemitere**: documentul vechi e deja marcat `STERS=1` inainte de rescriere
|
||||
(vezi B.6), asa ca filtrul `STERS=0` nu-l gaseste — cade tot pe `SEQ_IdFact.NEXTVAL`, exact ca azi;
|
||||
- **risc de coliziune**: daca NRACT/SERIE_ACT/DATAACT/ID_CTR ar coincide intamplator cu alt document
|
||||
nesters (nu neaparat cel pe care il reemitem), i-ar imprumuta ID_FACT-ul aceluia.
|
||||
Semnatura veche are 4 parametri de identitate diferiti de semnatura activa (1 parametru `V_GCS`,
|
||||
folosit doar ca "user"/context, nefolosit in corp) — deci reactivarea ar cere fie o supraincarcare,
|
||||
fie schimbarea semnaturii peste tot unde e chemata (vezi A.2).
|
||||
|
||||
### A.2 — Apelanti
|
||||
|
||||
**Un singur punct de apel in PL/SQL**: `PACK_CONTAFIN.pck:721`, in bucla din `SCRIE_IN_ACT`:
|
||||
|
||||
```
|
||||
pack_contafin.set_idfact(V_GCS);
|
||||
/* 25.02.2013 : se face cumularea id_fact pe listarea din RC/RJ
|
||||
PACK_CONTAFIN.SET_IDFACT(itemfact.dataact, itemfact.serie_act, itemfact.nract, itemfact.id_ctr);*/
|
||||
lnIdFact := get_idFact();
|
||||
```
|
||||
|
||||
buclat pe grupuri distincte `(NRACT, DATAACT, DATAIREG, SERIE_ACT, ID_CTR, ID_SET)` extrase din
|
||||
`ACT_TEMP WHERE ID_FACT = -1` (`:713`) — deci `-1` e sentinela "acest rand cere un `ID_FACT` nou".
|
||||
|
||||
`SCRIE_IN_ACT` insusi e apelat **doar pe drumul de scriere**, niciodata pe cel de stergere —
|
||||
in `finalizeaza_scriere_act_rul` (`:8449-8459`):
|
||||
|
||||
```
|
||||
if tnScrieSterge <> 2 then
|
||||
pack_contafin.SCRIE_IN_ACT(user);
|
||||
...
|
||||
else
|
||||
pack_contafin.STERGE_DIN_ACT(user, lnAn, lnLuna, tnCod, tnIdUtil, tnModificareNota);
|
||||
...
|
||||
```
|
||||
|
||||
Deci `SET_IDFACT` nu se atinge deloc la stergere — confirma ca azi cele doua operatii (stergere +
|
||||
reemitere) sunt independente unele de altele in privinta ID_FACT.
|
||||
|
||||
**Cine cheama `final_scriere_act_rul_local` / `SCRIE_IN_ACT` din VFP**: acelasi fisier
|
||||
`COMUN\programe\oscrie_in_fisiere.prg`, replicat identic in ~25-30 produse ROA (verificat prin grep
|
||||
pe `*.prg` in tot `D:\ROA`): `ROAFACTURARE`, `ROACONT` (output), `ROAGEST`, `ROAIMOB`, `ROAEFACTURA`,
|
||||
`ROADEVIZE`, `ROAVIN`, `ROACASA`, `ROASAL`, `ROAPRETURI`, `ROAOBINV`, `ROARESTAURANT`,
|
||||
`ROACOMENZI`, `ROAAPROV`, `ROASITOP`, `ROASITFIN`, `ROADECL`, `ROASALSPEC`, `ROABAVERT`,
|
||||
`ROACONIMPORT`, `ROAPRINT`, s.a. — plus doua variante care apeleaza `SCRIE_IN_ACT` direct, fara
|
||||
wrapper-ul local: `CONTAFIN2ORA\VFP2ORA\Programe\oscrie_in_fisiere.prg:367`,
|
||||
`ROADEFSALARII\COMUN\programe\oscrie_in_fisiere.prg:141`,
|
||||
`ROARESTAURANTCONFIG\COMUN_ROA\programe\oscrie_in_fisiere.prg:135`.
|
||||
Un al doilea import vechi (`COMUN\datemenu\xold\import_xdbf\oscrie_in_fisiere.prg`) apare in multe
|
||||
produse dar e **comentat integral** (`*!*`) — inactiv.
|
||||
|
||||
**Niciun apelant nu impune un `ID_FACT`.** Toti trimit `ID_FACT = -1` in `ACT_TEMP` (via
|
||||
`sql_temp_insert` in `oscrie_in_fisiere.prg:126-137`, care copiaza direct campurile din cursorul VFP,
|
||||
inclusiv `id_fact`) si lasa `SCRIE_IN_ACT` sa-l completeze din secventa. Confirmare suplimentara in
|
||||
`COMUN\clase\omodificari.vc2:4131-4132`: `"daca se schimba partenerul, se pune id_fact = -1 in loc
|
||||
de 0 pentru a se putea genera un nou id_fact"` — exact sentinela pe care se bazeaza bucla din
|
||||
`SCRIE_IN_ACT`.
|
||||
|
||||
### A.3 — Variabila/parametru existent pentru un ID_FACT dorit
|
||||
|
||||
**Nu exista azi.** `PACK_CONTAFIN` are o variabila de pachet `nIdFact` (setata de `SET_IDFACT`,
|
||||
citita de `GET_IDFACT`), dar e scrisa neconditionat de fiecare apel al lui `SET_IDFACT` — nu poate
|
||||
fi folosita ca "intrare" fara sa se schimbe corpul procedurii. Nu exista niciun `nid_...` global, nici
|
||||
alt parametru de sesiune care sa transmita un `ID_FACT` dorit lui `SET_IDFACT` sau lui `SCRIE_IN_ACT`.
|
||||
Ar trebui adaugata o variabila noua de pachet (ex. un "ID_FACT fortat", implicit `NULL`), citita
|
||||
**doar** in corpul activ al lui `SET_IDFACT`, consumata si resetata la prima folosire — vezi C.8.
|
||||
Acelasi diagnostic e deja notat in planul de proiect, `plan_13_unificare_formular_facturare.md:1717-1719`:
|
||||
`"ID_FACT. Se citeste inainte de stergere ... si se impune documentului nou, printr-un comutator
|
||||
folosit numai de regenerare. SET_IDFACT nu se schimba neconditionat — e cod comun intregii suite."`
|
||||
|
||||
B. DOCUMENTE la reemitere cu acelasi ID_FACT
|
||||
----------------------------------------------
|
||||
|
||||
### B.4 — INSERT sau UPDATE/MERGE azi
|
||||
|
||||
`PACK_CONTAFIN.pck:788-817`, in interiorul buclei din `SCRIE_IN_ACT`, cod activ (INSERT simplu):
|
||||
|
||||
```
|
||||
-- SCRIE IN DOCUMENTE
|
||||
lnTvaIncasare := case when itemfact.tva_incasare > 0 then 1 else 0 end;
|
||||
-- 25.02.2013 : am repus insertul pentru ca se face cumularea id_fact pe listarea din RC/RJ
|
||||
INSERT INTO DOCUMENTE
|
||||
(ID_DOC, ID_UTIL, DATAORA, TVA_INCASARE, SERIE_ACT, NRACT, DATAACT, DATAIREG, ID_CTR, ID_SET)
|
||||
VALUES
|
||||
(lnIdFact, lnID_Util, LD_DATAORA, lnTvaIncasare, itemfact.serie_act, itemfact.nract,
|
||||
itemfact.dataact, itemfact.dataireg, decode(itemfact.id_ctr, 0, NULL, itemfact.id_ctr),
|
||||
itemfact.id_set);
|
||||
/*
|
||||
-- modificare ROACONT v 2.4.0 (27.12.2012): am adaugat serie_act, nract, dataact
|
||||
-- modificare 21.02.2013: am adaugat id_ctr
|
||||
MERGE INTO DOCUMENTE A
|
||||
USING (SELECT lnIdFact as ID_DOC, itemfact.serie_act as SERIE_ACT, itemfact.nract as NRACT,
|
||||
itemfact.dataact as DATAACT,
|
||||
decode(itemfact.id_ctr, 0, NULL, itemfact.id_ctr) AS ID_CTR
|
||||
FROM DUAL) B
|
||||
ON (A.ID_DOC = B.ID_DOC)
|
||||
WHEN NOT MATCHED THEN
|
||||
INSERT (ID_DOC, ID_UTIL, DATAORA, TVA_INCASARE, SERIE_ACT, NRACT, DATAACT, ID_CTR)
|
||||
VALUES (B.ID_DOC, lnID_Util, LD_DATAORA, lnTvaIncasare, B.SERIE_ACT, B.NRACT, B.DATAACT, B.ID_CTR);*/
|
||||
```
|
||||
|
||||
Deci **azi e mereu un INSERT nou** — pe drumul normal (ID_FACT vine din secventa, mereu inexistent
|
||||
in `DOCUMENTE`) asta e corect, dar pentru cazul S9 (ID_FACT reutilizat, deja existent — chiar si
|
||||
soft-sters) INSERT-ul ar da eroare de unicitate (vezi B.5). Varianta `MERGE` comentata are doar
|
||||
`WHEN NOT MATCHED` — daca s-ar reactiva neschimbata, un `ID_DOC` deja existent ar fi pur si simplu
|
||||
ignorat (fara `WHEN MATCHED`), documentul reemis ramanand cu randul vechi din `DOCUMENTE`
|
||||
(cu `TVA_INCASARE`/`ID_SET` nemodificate si, mai grav, cu `STERS` neresetat — vezi B.6).
|
||||
|
||||
### B.5 — Constrangere de unicitate pe DOCUMENTE
|
||||
|
||||
DDL gasit in `D:\ROA\DATABASE\ALTELE\Creare_server_scripturi\FirmaNoua\fn_script.sql:5162-5198`:
|
||||
|
||||
```
|
||||
CREATE TABLE "DOCUMENTE" ("ID_DOC" NUMBER(20, 0) NOT NULL ENABLE, "DATAORA" DATE NOT NULL ENABLE,
|
||||
"ID_UTIL" NUMBER(5, 0) NOT NULL ENABLE, "STERS" NUMBER(1, 0) NOT NULL ENABLE,
|
||||
"DATAORAS" DATE, "ID_UTILS" NUMBER(5, 0) NOT NULL ENABLE) ...
|
||||
...
|
||||
CREATE UNIQUE INDEX "PK_DOCUMENTE" ON "DOCUMENTE" ("ID_DOC") ...
|
||||
ALTER TABLE "DOCUMENTE" ADD CONSTRAINT "PK_DOCUMENTE" PRIMARY KEY ("ID_DOC") USING INDEX ... ENABLE
|
||||
```
|
||||
|
||||
**Da: `ID_DOC` e PRIMARY KEY, unic.** (Coloanele `SERIE_ACT/NRACT/DATAACT/DATAIREG/ID_CTR/ID_SET/
|
||||
TVA_INCASARE` din INSERT-ul de la B.4 nu apar in acest script — script vechi de firma noua,
|
||||
probabil tabela a fost alterata ulterior cu `ALTER TABLE ADD COLUMN`; constrangerea de PK insa e
|
||||
stabila si nu are motiv sa fi fost scoasa.) Un `INSERT INTO DOCUMENTE (ID_DOC=...)` cu o valoare de
|
||||
`ID_DOC` care exista deja (chiar si `STERS=1`) arunca `ORA-00001: unique constraint (PK_DOCUMENTE)
|
||||
violated`. Nu am gasit alta constrangere de unicitate pe `DOCUMENTE`; nici FK de la `ACT.ID_FACT`
|
||||
catre `DOCUMENTE.ID_DOC` (`ACT.ID_FACT` e doar indexat, `IDX_ID_FACT`, `fn_script.sql:1637`, fara
|
||||
constrangere de integritate referentiala gasita) — deci reinserarea de randuri in `ACT` cu
|
||||
`ID_FACT` reutilizat **nu** e blocata de vreo constrangere pe `ACT` insusi; blocajul e exclusiv pe
|
||||
`DOCUMENTE`.
|
||||
|
||||
### B.6 — Randurile vechi la stergere: fizic sterse, STERS=1, sau raman
|
||||
|
||||
**Soft-delete pur, pe toate cele patru tabele relevante — nimic nu se sterge fizic.**
|
||||
|
||||
`STERGE_DIN_ACT` (`PACK_CONTAFIN.pck:1815-1859`), apelata din `finalizeaza_scriere_act_rul` cand
|
||||
`tnScrieSterge = 2`:
|
||||
|
||||
```
|
||||
PROCEDURE STERGE_DIN_ACT(V_GCS VARCHAR2, tnAn in number, tnLuna in number, tnCod in number,
|
||||
tnId_utils in number, tnTip IN NUMBER) is
|
||||
-- tnTip : 0 = modificare ; 1 = stergere
|
||||
...
|
||||
BEGIN
|
||||
...
|
||||
UPDATE /*+ index(ACT IDX_COD) */ ACT
|
||||
SET STERS = 1, DATAORAS = LD_DATAORA, ID_UTILS = lnId_util
|
||||
WHERE COD = tnCod and an = tnAn and luna = tnLuna;
|
||||
|
||||
IF lnTip = 1 THEN
|
||||
UPDATE DOCUMENTE
|
||||
SET STERS = 1, ID_UTILS = LNID_UTIL, DATAORAS = LD_DATAORA
|
||||
WHERE ID_DOC IN
|
||||
(SELECT /*+ index(ACT IDX_COD) */ DISTINCT ID_FACT
|
||||
FROM ACT
|
||||
WHERE COD = tnCod and an = tnAn and luna = tnLuna
|
||||
AND ID_FACT <> 0
|
||||
and ID_SET not in (90501, 90021)
|
||||
AND NOT (SCD = '4426' AND SCC = '4428')
|
||||
AND NOT (SCD = '4428' AND SCC = '4427'));
|
||||
END IF;
|
||||
|
||||
update act_temp set suma = -suma, suma_val = -suma_val;
|
||||
END STERGE_DIN_ACT;
|
||||
```
|
||||
|
||||
`ACT` -> `STERS=1` (randurile raman fizic, pe acelasi `COD`). Cand `tnTip=1` (stergere, nu simpla
|
||||
modificare), **`DOCUMENTE` primeste si el `STERS=1`**, pentru toate `ID_FACT` distincte gasite pe
|
||||
acel `COD` in `ACT` (cu exceptiile de mai sus pentru seturi/conturi speciale). Nicaieri nu am gasit
|
||||
un `UPDATE DOCUMENTE ... SET STERS = 0` — cautare exhaustiva in `PACK_CONTAFIN.pck` (`grep -i
|
||||
"UPDATE DOCUMENTE"`) a dat un singur rezultat, cel de mai sus. Deci nu exista azi niciun mecanism
|
||||
care sa "reinvie" un rand din `DOCUMENTE`.
|
||||
|
||||
La fel, `STERGE_DIN_RUL` / `STERGE_DIN_RUL_OBINV` (`:1861-1893`) fac `UPDATE ... SET STERS = 1` pe
|
||||
`RUL`/`RUL_OBINV`, fara stergere fizica.
|
||||
|
||||
Pentru `IREG_PARTENERI` si `JV2007` situatia e diferita in mod util pentru S9: acestea nu sunt copii
|
||||
1:1 ale documentului, ci **agregate recalculate prin `MERGE`** pe chei de business (an, luna, cont /
|
||||
`ID_FDOC`, `ID_FACT`, `NRACT`, `SERIE_ACT`, `DATAACT`, `DATAIREG`, `ID_PART`, ...) — vezi
|
||||
`SCRIE_JV_2007` (`:3328-3373+`) si `SCRIE_IN_IREG_PARTENERI`/`EXECUTA_SCRIE_IN_IREG` (`:7030+`,
|
||||
`:7607+`). Ambele sunt apelate **si pe drumul de scriere, si pe cel de stergere**
|
||||
(`finalizeaza_scriere_act_rul:8495-8578`, fara conditie pe `tnScrieSterge` in afara sursei: `act_temp`
|
||||
la scriere/stergere, `act` la refacere). La stergere, `sterge_document` (`:7699-8118`) reinsereaza in
|
||||
`ACT_TEMP` copii ale randurilor vechi din `ACT`/`RUL`/`RUL_OBINV`, **cu acelasi `ID_FACT` ca inainte**
|
||||
(coloana `id_fact` e copiata direct din sursa, nu resetata la `-1`), asa ca `MERGE`-ul din
|
||||
`SCRIE_JV_2007`/`SCRIE_IN_IREG_PARTENERI` recalculeaza (compenseaza) exact randul cu acel `ID_FACT`
|
||||
— nu creeaza duplicate. Deci reutilizarea `ID_FACT`-ului **nu produce dubluri in `JV2007`/
|
||||
`IREG_PARTENERI`**; problema e strict izolata la `DOCUMENTE`.
|
||||
|
||||
Nota din plan, care confirma independent aceasta zona de risc:
|
||||
`plan_13_unificare_formular_facturare.md:1737`: `"De verificat si daca DOCUMENTE primeste un al
|
||||
doilea rand pe acelasi ID_DOC sau il refoloseste."` — raspunsul, dupa aceasta cercetare: **niciuna
|
||||
din cele doua, in starea actuala a codului — ar arunca eroare de unicitate**, nu ar produce liniste
|
||||
tacuta cu duplicat sau refolosire.
|
||||
|
||||
C. Verdictul care conteaza
|
||||
----------------------------
|
||||
|
||||
### C.7 — Se poate reemite cu acelasi ID_FACT fara sa se strice nimic?
|
||||
|
||||
**DA, dar cu conditii** — niciuna dintre ele nu e implementata azi:
|
||||
|
||||
1. **Nu folosi varianta comentata a lui `SET_IDFACT` ca atare.** Cautarea `STERS=0` nu gaseste
|
||||
documentul (deja sters soft inainte de rescriere) si e vulnerabila la coliziuni pe chei de
|
||||
business partajate cu alt document. In loc de cautare, ID_FACT-ul trebuie **citit explicit
|
||||
inainte de stergere** (VFP il are deja in cursorul documentului editat) si **transmis** pe drumul
|
||||
de scriere — exact ce zice planul S9.
|
||||
2. **`SET_IDFACT` are nevoie de o cale de intrare noua, inerta implicit.** O variabila de pachet
|
||||
noua (ex. "ID_FACT fortat", `NULL` default) + o procedura noua de setat-o explicit inainte de
|
||||
scriere; corpul activ al `SET_IDFACT` verifica intai variabila, o consuma si o reseteaza; daca e
|
||||
`NULL` (cazul general — toate celelalte ~25-30 apeluri din suita), comportamentul e identic cu
|
||||
azi (secventa). Nu se toarna in semnatura existenta `SET_IDFACT(V_GCS)`, ca sa nu se schimbe
|
||||
apelul de la niciun alt caller.
|
||||
3. **Scrierea in `DOCUMENTE` trebuie sa devina un upsert real, cu `WHEN MATCHED`.** Nici INSERT-ul
|
||||
activ, nici MERGE-ul comentat (fara `WHEN MATCHED`) nu revigoreaza un rand soft-sters. E nevoie de
|
||||
un `MERGE ... WHEN MATCHED THEN UPDATE SET STERS = 0, DATAORA = ..., ID_UTIL = ..., TVA_INCASARE
|
||||
= ..., SERIE_ACT = ..., NRACT = ..., DATAACT = ..., DATAIREG = ..., ID_CTR = ..., ID_SET = ...`
|
||||
pe langa `WHEN NOT MATCHED THEN INSERT` (cazul normal, ID_FACT nou din secventa).
|
||||
4. **Succesiunea corecta**, in aceeasi tranzactie (deja planificata in S9 la nivel de VFP — vezi
|
||||
`plan_13...md:1713-1719`, mutarea stergerii in tranzactia deschisa de scriere): sterge documentul
|
||||
vechi (soft-delete, ca azi) -> seteaza variabila de la punctul 2 cu `ID_FACT`-ul citit inainte de
|
||||
stergere -> scrie documentul nou prin drumul obisnuit (`ACT_TEMP` cu `ID_FACT = -1` ca de obicei,
|
||||
dar acum grupul respectiv va primi ID_FACT-ul fortat in loc de unul nou) -> commit doar dupa ce
|
||||
ambele operatii au avut succes; orice eroare -> rollback total, documentul vechi ramane intact.
|
||||
|
||||
- **NU** (fara aceste conditii): `INSERT INTO DOCUMENTE` cu `ID_DOC` reutilizat, cat timp randul vechi
|
||||
e inca prezent cu `STERS=1`, arunca `ORA-00001` pe `PK_DOCUMENTE` — asta e dovada, nu presupunere
|
||||
(B.5 + B.4).
|
||||
|
||||
### C.8 — Suprafata de risc pentru restul suitei
|
||||
|
||||
- **~25-30 cai de apel** trebuie sa ramana neafectate: toate copiile `COMUN\programe\
|
||||
oscrie_in_fisiere.prg` din fiecare produs ROA (listate in A.2), plus cele 3 variante care apeleaza
|
||||
`SCRIE_IN_ACT` direct. Toate trec prin acelasi punct final: `SET_IDFACT(V_GCS)` in
|
||||
`PACK_CONTAFIN.pck:3037`.
|
||||
- **Garantia structurala propusa**: variabila de pachet noua, default `NULL`/inert, citita **doar**
|
||||
in interiorul corpului activ al lui `SET_IDFACT` (nu in semnatura, nu in vreun parametru propagat
|
||||
de apelanti); se seteaza explicit **doar** de codul de regenerare din ROAFACTURARE, chiar inainte
|
||||
de a incepe scrierea documentului reemis, si se consuma/reseteaza la prima citire (fie in
|
||||
`SET_IDFACT`, fie la finalul tranzactiei, ca sa nu "scurgi" valoarea catre urmatoarea scriere
|
||||
neinrudita din aceeasi sesiune Oracle — relevant daca `goExecutor`/conexiunea e reutilizata intre
|
||||
operatii ale aceluiasi utilizator in acelasi produs). Cu default inert si citire localizata strict
|
||||
in corpul lui `SET_IDFACT`, toate celelalte ~25-30 cai raman byte-for-byte identice cu azi — nu e
|
||||
nevoie sa se verifice fiecare apelant individual, garantia e la sursa (variabila neinitializata =
|
||||
comportament vechi).
|
||||
- **Riscul real nu e in `SET_IDFACT`, ci in `DOCUMENTE`.** Schimbarea INSERT->MERGE cu `WHEN MATCHED`
|
||||
la `PACK_CONTAFIN.pck:796-817` e riscanta pentru toata suita in alt sens: `WHEN MATCHED` s-ar
|
||||
declansa doar cand `ID_DOC` (generat din secventa) coincide cu unul existent — ceea ce, pe drumul
|
||||
normal, nu se intampla niciodata (secventa e monoton crescatoare, nu se repeta), deci practic
|
||||
ramura noua e inerta pentru toti apelantii actuali si activa doar cand variabila de la C.8 a fost
|
||||
setata explicit. Totusi orice modificare la acest INSERT/MERGE e in cod comun apelat de toata
|
||||
suita si trebuie testata pe cel putin un ciclu normal de scriere (fara variabila fortata) in
|
||||
fiecare produs care scrie facturi/note, nu doar in ROAFACTURARE.
|
||||
|
||||
Ramas de verificat pe baza vie
|
||||
--------------------------------
|
||||
- Structura curenta reala a tabelei `DOCUMENTE` (DDL-ul citat e dintr-un script vechi de "firma
|
||||
noua"; coloanele `SERIE_ACT/NRACT/DATAACT/DATAIREG/ID_CTR/ID_SET/TVA_INCASARE` folosite de
|
||||
INSERT-ul activ nu apar in acel DDL — probabil adaugate ulterior prin `ALTER TABLE`). De rulat pe
|
||||
baza vie: `SELECT column_name, nullable FROM user_tab_columns WHERE table_name = 'DOCUMENTE'
|
||||
ORDER BY column_id;` si `SELECT constraint_name, constraint_type FROM user_constraints WHERE
|
||||
table_name = 'DOCUMENTE';` — sa se confirme ca `PK_DOCUMENTE` e inca activa si ca nu exista alta
|
||||
constrangere aparuta intre timp.
|
||||
- Daca exista deja documente reale in productie unde `DOCUMENTE.ID_DOC` a fost, intr-un fel sau
|
||||
altul, reutilizat (ex. printr-o interventie manuala) — de verificat cu
|
||||
`SELECT id_doc, count(*) FROM documente GROUP BY id_doc HAVING count(*) > 1;` (ar trebui sa fie
|
||||
gol, dat fiind PK-ul, dar merita confirmat inaintea oricarei schimbari).
|
||||
- Comportamentul exact al `finalizeaza_stergere_nota`/`finalizeaza_modificare_nota` (`:8601+`,
|
||||
nu au fost citate integral aici) fata de `ID_FACT`/`ID_FACTD` — relevante daca reemiterea schimba
|
||||
seria/numarul, nu doar continutul (cazul S9 "de baza" e cel mai simplu: acelasi NRACT/SERIE_ACT).
|
||||
- Comportamentul lui `EXECUTA_SCRIE_TVA`/`SCRIE_JC_2007` (mentionate la `:8504-8540`, cazul an <
|
||||
2007 sau JC) fata de reutilizarea ID_FACT — nu au fost verificate in detaliu, doar `SCRIE_JV_2007`.
|
||||
- Nu am putut testa efectiv (fara acces la baza vie) daca `ORA-00001` e chiar eroarea ridicata de
|
||||
Oracle in acest scenariu exact — e o deductie directa din DDL (PK unic + INSERT simplu pe coloana
|
||||
respectiva), dar merita o rulare de proba pe o baza de test inainte de a proiecta solutia finala.
|
||||
553
docs/cercetare/idpol_comanda_contract.md
Normal file
553
docs/cercetare/idpol_comanda_contract.md
Normal file
@@ -0,0 +1,553 @@
|
||||
# De unde vine SCC-ul pe facturile din comandă/contract — și dacă modelul se poate refolosi pentru #13
|
||||
|
||||
Cercetare read-only, pe cod (VFP text + export `PACK_FACTURARE` curent) și pe baza vie (`MARIUSM_AUTO`
|
||||
pe `ROA_CENTRAL`, doar `SELECT`, 10.08.2026). Nu modifică nimic — nici `pack_facturare`, nici
|
||||
`ofacturare_comun.vc2`, nici `ofacturare_editare.prg`.
|
||||
|
||||
Continuă `docs\cercetare\coresp_cont_venchelt.md` (secțiunea 9) și `docs\plan_13_unificare_formular_facturare.md`
|
||||
(J-quater), care stabiliseră deja: FACT-024 verifică apartenența articolului la politica **de pe linie**,
|
||||
`id_pol` e parametru per-linie trimis de VFP (`V_ID_POL`, `adauga_articol_factura`), și pe contract
|
||||
articolul „liber" nu e de fapt fără politică — vine deja cu una atașată prin `cursor_contract`/`cursor_preturi`.
|
||||
|
||||
**Runda 2 (reformulare Marius):** prima trecere de mai jos (secțiunile D–C, păstrate ca dovadă) presupunea
|
||||
că întrebarea era „de unde vine `id_pol`". Marius a corectat premisa: el chiar emite facturi pe bază de
|
||||
document care **nu au** politică de preț și notă atașată **prin lanțul obișnuit** — ceea ce contrazicea
|
||||
concluzia inițială („`id_pol` gol → `FACT-024`, mereu"). Secțiunea 0 de mai jos reconciliază contradicția,
|
||||
pe cod și pe date reale, și e livrabilul principal al acestei runde. Sursa SQL folosită în ambele runde:
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (823.337 octeți — fișierul
|
||||
cu același nume din `docs\` a fost șters în timpul sesiunii anterioare; numerele de linie de mai jos sunt
|
||||
verificate direct pe fișierul din `SCRIPTURI_CLAR`, nu preluate din plan).
|
||||
|
||||
---
|
||||
|
||||
## 0. Reconcilierea contradicției — Marius are dreptate, dar mecanismul nu e cel presupus în runda 1
|
||||
|
||||
**Verdict, cu citat**: `id_pol` `NULL` **tot** blochează `contabilizeaza_articol` necondiționat — asta nu
|
||||
s-a schimbat, e reconfirmat mai jos. Ce s-a stabilit greșit în runda 1 e presupunerea că **orice** linie de
|
||||
document trece prin `contabilizeaza_articol`. **Nu e adevărat**: pachetul are cel puțin **două rute
|
||||
paralele**, folosite azi în producție (confirmate pe date reale), care scriu o notă contabilă de vânzare
|
||||
**fără `id_pol` și fără `CRM_POLITICI_PRETURI`** — pentru că nu apelează deloc `contabilizeaza_articol`.
|
||||
|
||||
### 0a. Reconfirmare: `contabilizeaza_articol` nu tolerează `id_pol NULL`, în nicio ramură
|
||||
|
||||
```sql
|
||||
-- ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7275-7302 (inceputul functiei, necondiționat)
|
||||
BEGIN
|
||||
BEGIN
|
||||
SELECT COMPUS, ID_POL_ART
|
||||
INTO V_COMPUS, V_ID_POL_ART
|
||||
FROM VCRM_POLITICI_PRET_ART
|
||||
WHERE ID_ARTICOL = detalii_articol.id_articol
|
||||
AND ID_POL = detalii_articol.id_pol; -- id_pol NULL => X=NULL => NO_DATA_FOUND, mereu
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
... SELECT NUME_LISTA_PRETURI INTO lcPolitica FROM CRM_POLITICI_PRETURI WHERE id_pol = detalii_articol.id_pol;
|
||||
RAISE_APPLICATION_ERROR(-20000, '... (FACT-024)');
|
||||
```
|
||||
Nu există niciun `IF detalii_articol.id_pol IS NULL THEN ...` înainte de acest bloc — e primul lucru pe
|
||||
care funcția îl face, necondiționat, pentru orice apel. **Bonus, deja semnalat în runda 7** (`plan_13...
|
||||
md:1451-1457`, reconfirmat aici pe fișierul curent): al doilea `SELECT` din `EXCEPTION` (cel care aduce
|
||||
`lcPolitica` pentru mesaj) filtrează tot pe `id_pol = detalii_articol.id_pol`, deci și el dă `NO_DATA_FOUND`
|
||||
când `id_pol` e `NULL` — utilizatorul nu vede mesajul formatat „FACT-024", ci un `ORA-01403` brut,
|
||||
necaptat. În ambele cazuri: **eroare, tranzacție întreruptă, nimic scris**. Deci dacă o linie chiar ajunge
|
||||
la `contabilizeaza_articol` cu `id_pol` gol, nu există azi nicio cale silențioasă de succes.
|
||||
|
||||
### 0b. Ce am găsit real, pe date vii — 384 din 1113 linii `VANZARI_DETALII` active (34%) au `ID_POL` `NULL`
|
||||
|
||||
```sql
|
||||
select count(*), sum(case when id_pol is null then 1 else 0 end) from vanzari_detalii where sters=0;
|
||||
-- 1113 384
|
||||
```
|
||||
Deci liniile facturate fără `id_pol` **există masiv** în producție — nu e un caz exotic. Împărțite pe
|
||||
`VANZARI.TIP`:
|
||||
|
||||
| `TIP` | linii fără `id_pol` | din ele, rânduri `ACT` cu `SCC` populat | interpretare |
|
||||
|---|---|---|---|
|
||||
| `-12` | 97 | 588/604 | **ROAAUTO** (confirmat, `tip=-12` e explicit ROAAUTO — `plan_13...md:1710,1735`) |
|
||||
| `-1..-13` (restul) | ~140 | majoritar populat | variante storno/aviz ale documentelor de mai jos, nu cercetate individual |
|
||||
| `2` (**contract**) | 19 | populat, vezi 0c | facturare pe bază de contract, **rată/scadențar**, nu articol |
|
||||
| `1` (listă prețuri) | 1 | — | un singur caz, neexplorat separat |
|
||||
| `51` | 121 | 179/275 (cumulat) | tip necunoscut în codul VFP citit — posibil retail/POS, neidentificat |
|
||||
| **`3` (comandă, ROAFACTURARE)** | **0 din 66** | — | **niciun caz** — vezi 0d |
|
||||
|
||||
`select count(*), sum(case when id_pol is null then 1 else 0 end) from vanzari_detalii d join vanzari v on v.id_vanzare=d.id_vanzare where d.sters=0 and v.sters=0 and v.id_comanda is not null` → **66 / 0**. Pe ruta
|
||||
`COMENZI` a lui ROAFACTURARE (secțiunea A de mai jos), **`id_pol` nu e niciodată gol, pe datele disponibile.**
|
||||
Contradicția lui Marius nu se reproduce pe acest obiect precis.
|
||||
|
||||
### 0c. Mecanismul găsit: `contabilizeaza_rata` — notă contabilă direct din contract, fără politică, fără `id_pol`
|
||||
|
||||
Contractele cu facturare pe **rate/scadențar** (`CONTRACTE.OPT_FACTURARE IN (1,2)`) nu trec liniile prin
|
||||
`contabilizeaza_articol` — trec prin o funcție **separată**, `contabilizeaza_rata`:
|
||||
```sql
|
||||
-- ff_...:7549-7597
|
||||
FUNCTION contabilizeaza_rata(detalii_rata VANZARI_DETALII_TEMP%ROWTYPE) RETURN NUMBER IS
|
||||
BEGIN
|
||||
BEGIN
|
||||
SELECT NVL(B.ID_VENCHELT, pack_facturare.nid_venchelt), ..., C.SCD, C.ASCD, C.SCC, C.ASCC, C.CU_TVA, C.IN_VALUTA
|
||||
INTO ...
|
||||
FROM CONTRACTE A
|
||||
LEFT JOIN CRM_NOTE_VANZARI B ON A.ID_NOTA = B.ID_NOTA -- <- nota vine din CONTRACTE.ID_NOTA, NU din id_pol
|
||||
LEFT JOIN NOTE_CONTABILE C ON B.ID_SET = C.ID_SET
|
||||
WHERE A.STERS = 0 AND A.ID_CTR = detalii_rata.id_ctr AND B.STERS = 0;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
RAISE_APPLICATION_ERROR(-20000, 'Nu sunt configurate notele de vanzare pentru acest contract!');
|
||||
END;
|
||||
...
|
||||
V_INCASAT_CALCUL := V_INCASAT_CALCUL + pack_facturare.scrie_nota(..., V_SCD, V_ASCD, V_SCC, V_ASCC, ...);
|
||||
```
|
||||
`CONTRACTE` are coloană proprie `ID_NOTA` (confirmat pe schema vie: `NUMBER`, nullable) — **contractul însuși
|
||||
duce nota contabilă**, ales o singură dată la configurarea lui, independent de orice `id_pol`/politică de
|
||||
preț per articol. Confirmă exact tiparul din `cursor_contract` (secțiunea B mai jos, ramura „rată" a
|
||||
cursorului, `ff_...:2837-2893`): `NULL as id_articol, ..., NULL AS ID_POL` — liniile de rată **nu au
|
||||
niciodată `id_pol`, prin construcție**, pentru că nu sunt articole din nomenclator, sunt rate de scadențar.
|
||||
|
||||
**Confirmat pe o factură reală** (`MARIUSM_AUTO`, 10.08.2026):
|
||||
```sql
|
||||
-- id_vanzare 360, tip 2 (contract), linia din VANZARI_DETALII: id_articol NULL, id_pol NULL
|
||||
-- ACT pentru id_fact=5039903:
|
||||
-- ID_ACT SCD ASCD SCC ASCC SUMA EXPLICATIA
|
||||
-- 102204 4111 11 704 300 RATA 1
|
||||
-- 102205 4111 11 4427 57 TVA RATA 1
|
||||
```
|
||||
O factură reală, cu o linie fără `id_articol` și fără `id_pol`, **cu notă contabilă scrisă corect** (`SCD
|
||||
4111`, `SCC 704`) — exact dovada cerută. Mecanismul: **nota nu vine prin politică deloc**, vine direct din
|
||||
`CONTRACTE.ID_NOTA`, o singură dată per contract, la configurare — un tipar „un document duce o notă fixă",
|
||||
nu „fiecare linie duce o politică de preț".
|
||||
|
||||
### 0d. De ce nu se reproduce pe `COMENZI` (ROAFACTURARE): tabelul nu are echivalentul lui `ID_NOTA`
|
||||
|
||||
```sql
|
||||
select column_name from user_tab_columns where table_name='COMENZI';
|
||||
-- ID_COMANDA, NR_COMANDA, DATA_COMANDA, ID_PART, ID_GESTIUNE, ID_SECTIE, ID_SECTIE2, ID_CTR, ... (29 coloane)
|
||||
-- nicio ID_NOTA, nicio ID_POL, nicio coloana de contabilizare
|
||||
```
|
||||
`COMENZI` (antetul) nu duce nicio informație contabilă — singura legătură posibilă e `ID_CTR` (dacă
|
||||
comanda vine dintr-un contract). `COMENZI_ELEMENTE.ID_POL` rămâne singurul canal, și e `NOT NULL` (secțiunea
|
||||
A). Deci pe **acest** obiect, mecanismul „notă fără politică" descris de Marius **nu există** — dacă
|
||||
experiența lui vine de aici, ar însemna fie (a) o versiune de bază/schemă diferită de `MARIUSM_AUTO`, fie
|
||||
(b) o factură pe care a facturat-o efectiv prin ruta **contract cu rate** (secțiunea 0c) sau prin **ROAAUTO**
|
||||
(`tip -12`, deviz — tipar deja documentat în `coresp_cont_venchelt.md` §9b: `adauga_articol_factura_deviz`
|
||||
→ `scrie_in_vanzari`, fără `contabilizeaza_articol`, cu notă scrisă separat de fiecare produs), și a numit-o
|
||||
generic „factură pe bază de comandă". **Nu pot decide între aceste ipoteze fără să întreb** — dovada de cod
|
||||
și de date arată clar CARE mecanisme există și niciunul nu e pe `COMENZI` propriu-zis.
|
||||
|
||||
### 0e. Variantele din brief, verdict pe fiecare
|
||||
|
||||
- *„liniile de comandă primesc totuși un `id_pol` de undeva"* — **confirmat, dar nu ascuns**: da, primesc,
|
||||
documentat deja în secțiunea A/D veche, e vizibil în UI (`v_articole`), nu explică „fără politică".
|
||||
- *„`contabilizeaza_articol` nu e apelată pe ruta comandă, ci altă procedură"* — **fals pentru `COMENZI`**
|
||||
(`ofacturare.prg:292-293` → `cursor_comanda` → `crsarticole` → `do_scrie_articole` → `adauga_articol_factura`
|
||||
→ `contabilizeaza_articol`, ruta normală); **adevărat pentru contract-cu-rate** (`contabilizeaza_rata`) și
|
||||
pentru ROAAUTO/ROAACNPRO (proceduri proprii, deja documentate).
|
||||
- *„există o ramură de fallback în `contabilizeaza_articol` care ajunge la notă fără politică"* — **fals**,
|
||||
reconfirmat la 0a; funcția nu are asemenea ramură, blochează necondiționat.
|
||||
- *„FACT-024 se ridică doar când `id_pol` e nenul dar articolul nu e membru, `id_pol` nul merge pe alt
|
||||
drum"* — **fals ca „alt drum în aceeași funcție"**; **adevărat ca „alt drum = altă funcție"** (0c/0d).
|
||||
|
||||
---
|
||||
|
||||
## D. Răspunsul la întrebarea lui Marius, în cinci rânduri (actualizat după reconciliere)
|
||||
|
||||
**Depinde care „comandă".** Pe obiectul `COMENZI`/`COMENZI_ELEMENTE` al ROAFACTURARE (facturare „pe bază
|
||||
de comandă", tip 3), SCC-ul vine tot din `NOTE_CONTABILE.SCC` prin `id_pol` — **`id_pol` nu lipsește
|
||||
niciodată**, e coloană `NOT NULL` (confirmat pe schemă și pe date: 0 din 6868/66 linii fără el), populată la
|
||||
adăugarea articolului dintr-o listă deja restrânsă la o singură politică (`v_articole`/`com_vpreturi_utilizator`),
|
||||
politică aleasă o dată pe comandă. **Dacă experiența lui Marius vine din facturarea contractelor cu rate
|
||||
(scadențar, `OPT_FACTURARE IN (1,2)`) sau din ROAAUTO**, atunci da, există o rută reală, azi în producție,
|
||||
care scrie nota **fără nicio politică de preț**: pe contracte-cu-rate, nota vine direct din
|
||||
`CONTRACTE.ID_NOTA` (o coloană pe contract, aleasă o singură dată la configurarea lui, independentă de orice
|
||||
`id_pol` per linie) — `contabilizeaza_rata`, nu `contabilizeaza_articol`. Pe ROAAUTO/ROAACNPRO, nota o scrie
|
||||
o procedură proprie a produsului (`pack_acn.salveaza_regdoc` etc.), tot în afara lanțului `CRM_POLITICI_PRETURI`.
|
||||
**Aceste rute nu sunt „`id_pol` gol tratat cu grijă" — sunt căi care nu ating deloc `id_pol`/`contabilizeaza_articol`.**
|
||||
Pe contract-cu-**articole** (nu rate), tiparul rămâne cel din runda 1: `CTR_ARTICOLE.ID_POL_ART` → membru
|
||||
al unei politici reale, ca și pe comandă.
|
||||
|
||||
## E. Se poate refolosi „același model" pentru articolul ad-hoc din #13?
|
||||
|
||||
**Parțial. Mecanismul de jos (RPC + „apartenența e garantată prin construcția listei") e direct
|
||||
transplantabil — dar e deja exact ce propune rețeta în 4 pași din J-quater, nu o alternativă mai simplă
|
||||
la ea. Partea care ar simplifica cu adevărat lucrurile — „ia pur și simplu politica curentă și adaugă
|
||||
articolul în ea" — e decizia 24, deja retrasă de Marius, din motive care rămân valabile.**
|
||||
|
||||
Detaliat:
|
||||
|
||||
1. **Ce arată comandă/contract, tehnic**: operatorul nu alege niciodată `id_pol` pentru un articol anume —
|
||||
alege **o politică** (una dintre cele la care are drept, via `UTILIZATORI_ROL_INTERN` →
|
||||
`POLITICI_GRUPURI` → `CRM_POLITICI_PRETURI`), iar din acel moment orice articol afișat spre alegere e
|
||||
deja membru al ei (`com_vpreturi_utilizator`/`cPol_pret_art` filtrează `id_pol_art`/`id_pol IS NOT NULL`
|
||||
la sursă). Verificarea FACT-024 „trece" pe comandă/contract **nu pentru că ar exista un fallback** — ci
|
||||
pentru că nu există cale, în UI, de a ajunge la un articol care nu e deja membru. E o garanție de
|
||||
construcție a listei, nu o validare separată.
|
||||
2. **Acest tipar exact e deja documentat, ca fezabil, în `coresp_cont_venchelt.md` secțiunea „e)" / J-quater
|
||||
punctul 3** — RPC `pack_preturi.adauga_politica_pret_art` (deja folosit în producție,
|
||||
`ofacturare.vc2:15551-15587`) pentru „asigură apartenența", plus trimiterea lui `id_pol` neschimbat la
|
||||
`adauga_articol_factura`. Nu e nimic nou de inventat aici — comanda/contractul confirmă că tiparul
|
||||
„RPC + listă pre-filtrată" funcționează în producție de multă vreme, dar **planul A din J-quater deja îl
|
||||
folosește**. Refolosirea nu elimină pasul, îl confirmă.
|
||||
3. **Ce NU rezolvă modelul comandă/contract e alegerea SCC-ului corect per articol** — pe comandă/contract
|
||||
politica e **una singură, fixă**, aleasă pentru tot documentul/sesiunea, cu un singur `SCC` (orice ar
|
||||
fi el) pentru toate articolele adăugate așa. Aplicat identic pe `caut_articol` (restricționează
|
||||
rezultatele căutării din nomenclator la politica curentă a operatorului), rezultatul ar fi
|
||||
**exact ceea ce există deja azi ca „Cauta in lista de preturi…"** — nu rezolvă cazul pe care S4g/#13 îl
|
||||
cere explicit: un articol care **nu e membru al niciunei politici** (`plan_13...md:1711-1716`: „din
|
||||
nomenclator se oferă și articole gestionabile, și negestionabile"; „un articol fără `ID_POL` nu ajunge
|
||||
la cont gol, ci la eroare").
|
||||
4. **„Alege o politică fixă și pune articolul în ea" e literalmente decizia 24, deja retrasă**
|
||||
(`plan_13...md:926-930`): *„Decizia 24: articolul ales din nomenclator primește `id_pol`-ul unei
|
||||
politici de pret implicite. RETRASĂ în runda 8... prețul lui era o întrebare de configurare cu
|
||||
contabilul (care politică, cu ce `SCC`, una sau mai multe) și un cont de venit uniform pentru orice
|
||||
articol adăugat așa. Marius a ales în loc derivarea contului. Nu se reintroduce."* Motivul retragerii
|
||||
nu s-a schimbat: o politică unică (fie ea „a operatorului", ca pe comandă) dă un **singur** cont de
|
||||
venit oricărui articol, indiferent de natura lui (marfă/producție/serviciu) — exact opusul deciziei 27,
|
||||
care cere `SCC` diferențiat după clasa contului de gestiune al articolului (`707`/`711`/`702`/`703`/
|
||||
`7015`/`7018`/`704`).
|
||||
5. **Deci ce rămâne de făcut e exact pasul central din rețeta în 4 pași**: nu „cum trimit un `id_pol`
|
||||
valid la Oracle fără să ating `pack_facturare`" (asta e deja rezolvat, și demonstrat funcțional de
|
||||
comandă/contract) — ci „**care** politică, cu **care** `SCC`, pentru **acest** articol anume" — acolo
|
||||
comanda/contractul nu ajută, pentru că ele nu rezolvă niciodată această întrebare: politica le vine
|
||||
gata aleasă, de operator, o singură dată, nu calculată per articol.
|
||||
|
||||
**Verdict la „mă gândeam la același model" (dacă modelul e comandă/contract-cu-articole)**: da, la nivel de
|
||||
mecanism (RPC de inserare + membership garantat prin listă filtrată) — dar acel nivel era deja acoperit de
|
||||
planul A/decizia 27-bis. Nu, la nivelul la care ar simplifica ceva („nu mai calculăm SCC per articol, luăm
|
||||
politica gata aleasă") — pentru că acela e exact decizia 24, respinsă cu un motiv care rămâne valabil.
|
||||
|
||||
### E-bis. Dar există un al treilea model, mai simplu — cel din `contabilizeaza_rata` (secțiunea 0c)
|
||||
|
||||
Reconcilierea de la secțiunea 0 arată un tipar pe care raportul din runda 1 nu-l luase în calcul: **un
|
||||
document poate duce el însuși o notă contabilă fixă, complet în afara `CRM_POLITICI_PRETURI`, iar
|
||||
`pack_facturare` are deja o funcție care face exact asta** (`contabilizeaza_rata`, sursa `CONTRACTE.ID_NOTA`).
|
||||
Aplicat la #13, ideea ar fi: nu mai deriva `SCC` per articol și nu mai caut/construi o politică tehnică — scrie
|
||||
nota direct, cu conturile calculate în VFP (regula deciziei 27: `CORESP_CONT_VENCHELT`/`NOM_ARTICOLE.CONT`/
|
||||
`704`), printr-o cale de scriere **paralelă**, ca și pentru rate.
|
||||
|
||||
**De ce nu se poate fără să ating `pack_facturare`, și asta contează, dat fiind decizia 27-bis**:
|
||||
- `contabilizeaza_rata` există deja, dar e legată strict de `VANZARI_DETALII_TEMP` cu semantică de „rată de
|
||||
contract" (`detalii_rata.id_ctr` obligatoriu în interogare) — nu e apelabilă pentru un rând de articol
|
||||
obișnuit (`id_articol` populat, `id_pol` gol) fără o funcție **nouă**, analogă, în `pack_facturare`.
|
||||
- Sursa notei la rată e `CONTRACTE.ID_NOTA` — o coloană pe un document care **există deja** și **se
|
||||
configurează o dată** (la crearea contractului). Pentru articolul ad-hoc de pe formularul unificat nu
|
||||
există azi un asemenea „document-sursă cu notă fixă" — ar trebui inventat (ex. o coloană similară pe
|
||||
antetul facturii, sau parametru nou la `adauga_articol_factura`) — tot cod nou în `PACK_FACTURARE`.
|
||||
- **Constrângerea „pack_facturare rămâne neatins" (decizia 27-bis) exclude explicit acest drum.** Dacă
|
||||
Marius e dispus s-o relaxeze, tiparul `contabilizeaza_rata` e un precedent real, mai simplu decât rețeta
|
||||
în 4 pași — pentru că elimină complet nevoia unei „politici tehnice" și a RPC-ului de apartenență
|
||||
(`pack_preturi.adauga_politica_pret_art`): SCC-ul ar merge direct în `scrie_nota`, fără ocolul prin
|
||||
`CRM_POLITICI_PRET_ART`. Dacă nu, rămâne exact ce spunea verdictul din runda 1: rețeta în 4 pași (planul
|
||||
A, tot în VFP) rămâne singura variantă compatibilă cu constrângerea.
|
||||
|
||||
**Verdict final, actualizat**: modelul comandă/contract (runda 1) nu ajută, motivele rămân cele deja arătate.
|
||||
Dar există un al doilea model real, comandă**-rate**/contract-rate, care **ar** ajuta — cu prețul de a
|
||||
renunța la „`pack_facturare` neatins". E o decizie de produs pentru Marius, nu o concluzie tehnică unică —
|
||||
raportul pune ambele opțiuni pe masă, cu costul lor exact.
|
||||
|
||||
**Corecție importantă, din verificarea independentă de mai jos („Completare de la sesiunea principală"):**
|
||||
politica `7 DISCOUNT` (870 linii de comandă, `SCD=667`/`SCC=4111`, notă `2 DISCOUNT`) e deja, în producție,
|
||||
o **politică tehnică folosită doar ca rutare contabilă**, nu ca listă comercială — exact tiparul „o politică
|
||||
per scop contabil" pe care planul A/decizia 27-bis îl propune (nu decizia 24, care era o politică unică
|
||||
pentru *orice* articol ad-hoc, indiferent de natură). Deci precedentul operațional pentru „politică tehnică,
|
||||
una per destinație contabilă" **există deja pe ruta comandă** — mai puternic decât `gnId_pol_pret_stoc`
|
||||
(care e un singur cont pentru tot stocul). **Dar** aceeași verificare arată că garanția „apartenență prin
|
||||
construcția listei" (punctul 1 de mai sus) **nu e etanșă pe date**: 37 din 6868 linii active de comandă au
|
||||
un articol care nu (mai) e membru al politicii înregistrate pe linie — verificare, cauză și comportament la
|
||||
facturare, deschise, vezi secțiunea finală.
|
||||
|
||||
---
|
||||
|
||||
## A. Ruta COMANDĂ
|
||||
|
||||
### A.1 — `cursor_comanda` selectează `ID_POL`? Da, direct de pe linia comenzii
|
||||
|
||||
```sql
|
||||
-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2996-3054 (ramura factură)
|
||||
OPEN V_CURSOR FOR
|
||||
SELECT ROWNUM as id_c,
|
||||
A.ID_ARTICOL,
|
||||
NULL AS LOT,
|
||||
NULL as SERIE,
|
||||
A.ID_POL, -- <- direct din COMENZI_ELEMENTE, nu derivat
|
||||
A.ID_VALUTA, ...
|
||||
FROM COMENZI_ELEMENTE A
|
||||
LEFT JOIN CRM_POLITICI_PRET_ART B
|
||||
ON A.ID_POL = B.ID_POL AND A.ID_ARTICOL = B.ID_ARTICOL
|
||||
...
|
||||
WHERE A.STERS = 0 AND A.ID_COMANDA = V_ID_COMANDA ...
|
||||
```
|
||||
(identic la `:3084-3140` pentru ramura aviz, `V_TIP > 20`). `A.ID_POL` e coloană directă pe
|
||||
`COMENZI_ELEMENTE` — `LEFT JOIN CRM_POLITICI_PRET_ART B` de mai jos **nu** derivă `id_pol`, doar aduce
|
||||
`DISCOUNT_UNITAR`/`PROC_TVAV` pentru acea combinație `(id_pol, id_articol)`.
|
||||
|
||||
### A.2 — Unde e stocat: `COMENZI_ELEMENTE.ID_POL`, coloană `NOT NULL`
|
||||
|
||||
Confirmat pe schema vie (`MARIUSM_AUTO`, `ROA_CENTRAL`, 10.08.2026):
|
||||
```sql
|
||||
select column_name, data_type, nullable from user_tab_columns where table_name='COMENZI_ELEMENTE';
|
||||
-- ID_POL NUMBER N <- NOT NULL
|
||||
select count(*), sum(case when id_pol is null then 1 else 0 end) from comenzi_elemente where sters=0;
|
||||
-- 6868 0
|
||||
```
|
||||
**`ID_POL` e obligatoriu la nivel de constrângere de tabel**, nu doar „de obicei populat" — și cele 6868
|
||||
linii active din baza vie confirmă 0 excepții. E o coloană de **linie**, nu de antet (nu există `ID_POL`
|
||||
pe `COMENZI`).
|
||||
|
||||
### A.3 — Cine îl pune acolo la crearea comenzii
|
||||
|
||||
Nu e o alegere liberă per articol — e moștenit dintr-o **politică aleasă o singură dată pe comandă**:
|
||||
|
||||
```
|
||||
COMUN\clase\ocomenzi.vc2:1849-1870 (do_modifica, la deschiderea/crearea comenzii)
|
||||
Do Case
|
||||
Case Inlist(loRec.interna,2,5)
|
||||
If lnTip = 0 And Reccount('crscomanda_curenta')>0
|
||||
lnIdPol = id_pol && preia politica de pe comanda existenta
|
||||
Else
|
||||
loCauta = caut_politici_curente_utilizator() && cautare manuala, comanda noua
|
||||
If !Empty(Nvl(loCauta.id_pol,0))
|
||||
lnIdPol = loCauta.id_pol
|
||||
Else
|
||||
Return
|
||||
Endif
|
||||
Endif
|
||||
update_articole_politica(lnIdPol)
|
||||
Case loRec.interna = 3
|
||||
update_articole_politica_comenzi([IDPOLITICAPRET]) && optiune implicita globala
|
||||
Otherwise
|
||||
update_articole_politica_comenzi([ID_LISTA_PRETURI_PV]) && optiune implicita globala
|
||||
Endcase
|
||||
```
|
||||
Deci: pentru un tip de comandă (`interna` 2/5) operatorul **caută explicit** o politică
|
||||
(`caut_politici_curente_utilizator`); pentru celelalte tipuri, politica vine dintr-o **opțiune globală**
|
||||
(`gnIdPoliticaPret`/`gnId_lista_preturi_PV`, citite în `extrage_optiuni_firma`,
|
||||
`update_comenzi.prg:16-29`). Nu se moștenește de la client.
|
||||
|
||||
Politica aleasă filtrează lista de articole disponibile de adăugat:
|
||||
```
|
||||
COMUN\programe\update_comenzi.prg:31-60 (update_articole_politica)
|
||||
select p.id_articol,p.id_pol,p.pret,... from com_vpreturi_utilizator p ...
|
||||
where p.id_util = <<gnIdUtil>> and p.id_pol = <<tnIdPol>>
|
||||
```
|
||||
```sql
|
||||
-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\01\ff_2026_01_21_01_FACTURARE.sql:3-30
|
||||
create or replace view com_vpreturi_utilizator as
|
||||
select a.id_util, b.id_politica as id_pol, ..., d.id_articol, d.pret, ...
|
||||
from utilizatori_rol_intern a
|
||||
left join politici_grupuri b on a.id_grup = b.id_grup
|
||||
left join crm_politici_preturi c on b.id_politica = c.id_pol
|
||||
left join crm_politici_pret_art d on c.id_pol = d.id_pol
|
||||
left join nom_articole e on d.id_articol = e.id_articol
|
||||
where a.sters = 0 and b.sters = 0 and ... and d.id_pol is not null and ...
|
||||
```
|
||||
`v_articole` (grid-ul de adăugare articole pe comandă, `ocomenzi.vc2:4005-4014`,
|
||||
`RecordSource = "v_articole"`) e populat strict din acest view, **filtrat `d.id_pol IS NOT NULL`** — deci
|
||||
orice articol afișat spre alegere e deja membru garantat. La alegere (`do_adauga`,
|
||||
`ocomenzi.vc2:4645-4702`) și la salvare (`do_scrie_articole`, `:4864-4899`):
|
||||
```
|
||||
ocomenzi.vc2:4885-4893
|
||||
Scatter Name poArticol
|
||||
...
|
||||
lcSql = [begin pack_comenzi.adauga_articol_comanda(] + Alltrim(Str(This.nId)) + [,] + ;
|
||||
Alltrim(Str(poArticol.id_articol)) + [,] + Alltrim(Str(poArticol.id_pol)) + [,] + ...
|
||||
```
|
||||
`poArticol.id_pol` (scatter din `v_articole`) merge neschimbat în `COMENZI_ELEMENTE.ID_POL`. Nu există în
|
||||
`ocomenzi.vc2` niciun al doilea punct de adăugare a articolelor pe comandă (nicio cale către `caut_articol`
|
||||
de nomenclator liber) — `v_articole` e singura sursă.
|
||||
|
||||
### A.4 — Ce se întâmplă când linia de comandă are `ID_POL` gol la facturare
|
||||
|
||||
**Nu se poate întâmpla, prin construcție dublă**: (a) `COMENZI_ELEMENTE.ID_POL` e `NOT NULL` la nivel de
|
||||
Oracle — un `INSERT` cu `id_pol` gol ar da `ORA-01400`, nu ajunge niciodată să fie facturat; (b) singurul
|
||||
punct de inserare din VFP (`pack_comenzi.adauga_articol_comanda`, apelat cu `poArticol.id_pol` din
|
||||
`v_articole`) nu poate produce `id_pol` gol, pentru că sursa (`com_vpreturi_utilizator`) filtrează deja
|
||||
`id_pol IS NOT NULL`. Verificat pe date reale: 0 din 6868 linii active. Spre deosebire de nomenclator
|
||||
(`caut_articol`, unde coloana `id_pol` **nu există deloc** în SELECT), pe comandă întrebarea „ce se
|
||||
întâmplă la gol" nu are un răspuns de cod (fallback/eroare) pentru că premisa nu apare niciodată.
|
||||
|
||||
---
|
||||
|
||||
## B. Ruta CONTRACT
|
||||
|
||||
Produsul e separat, **`D:\ROA\ROACONTRACTE`** (există în arbore, alături de `ROAFACTURARE`).
|
||||
|
||||
### B.1 — Cursorul de facturare selectează `ID_POL`? Da, dar prin `ID_POL_ART`, nu direct
|
||||
|
||||
```sql
|
||||
-- D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2718-2836 (cursor_contract, V_CURSOR2 = grd_contracte)
|
||||
SELECT ..., id_pol, ...
|
||||
FROM (SELECT A.ID_CTR, C.ID_ARTICOL, NULL as id_rata, C.ID_POL, ...
|
||||
FROM CONTRACTE A
|
||||
LEFT JOIN CTR_ARTICOLE B ON A.ID_CTR = B.ID_CTR
|
||||
LEFT JOIN CRM_POLITICI_PRET_ART C ON B.ID_POL_ART = C.ID_POL_ART -- <- cheia stocata e ID_POL_ART
|
||||
LEFT JOIN NOM_ARTICOLE E ON C.ID_ARTICOL = E.ID_ARTICOL ...
|
||||
```
|
||||
Grid-ul principal de adăugare articole pe contract (`crsarticole`, folosit pentru „adăugare liberă") **nu**
|
||||
vine din acest cursor — vine dintr-un apel separat, în interiorul aceleiași proceduri:
|
||||
```sql
|
||||
-- ff_...:2940-2948
|
||||
pack_facturare.cursor_preturi(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_ID_GESTIUNE_INIT,
|
||||
V_LUNA, V_AN, V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR);
|
||||
```
|
||||
adică **exact aceeași sursă ca „lista de prețuri" (secțiunea C)** — confirmă din nou concluzia din runda
|
||||
anterioară (`retur_si_lista_preturi.md` B7): grid-ul „liber" de pe contract nu e liber de politică, e lista
|
||||
de prețuri normală a operatorului.
|
||||
|
||||
### B.2 — Unde e stocat pe contract: `CTR_ARTICOLE.ID_POL_ART` (linie, nullable)
|
||||
|
||||
```sql
|
||||
select column_name, nullable from user_tab_columns where table_name='CTR_ARTICOLE';
|
||||
-- ID_POL_ART Y <- nullable, spre deosebire de COMENZI_ELEMENTE.ID_POL
|
||||
select count(*), sum(case when id_pol_art is null then 1 else 0 end) from ctr_articole;
|
||||
-- 27 6
|
||||
```
|
||||
Spre deosebire de comandă, contractul stochează **`ID_POL_ART`** — FK direct la rândul din
|
||||
`CRM_POLITICI_PRET_ART` (articol+politică), nu la politică ca atare — și coloana **e nullable**, fără
|
||||
constrângere de tabel. Pe datele reale (bază de dezvoltare, eșantion mic — 27 rânduri în total, deci
|
||||
**nu extrapolez procentul la producție**), 6 din 27 rânduri nu au `id_pol_art`. Dacă un asemenea rând ar
|
||||
ajunge selectat spre facturare prin `V_CURSOR2`/`grd_contracte` (nu prin `crsarticole`, care e mereu lista
|
||||
de prețuri), `LEFT JOIN ... ON B.ID_POL_ART = C.ID_POL_ART` nu găsește nimic, `id_pol` iese `NULL` în
|
||||
cursor — același drum spre FACT-024 ca la nomenclator. **Neverificat**: dacă UI-ul (`grd_contracte`) permite
|
||||
efectiv selecția unei asemenea linii spre facturare sau o filtrează/dezactivează — nu am urmărit acel cod
|
||||
de grid, în afara bugetului acestei runde.
|
||||
|
||||
### B.3 — Cine îl pune acolo la crearea contractului
|
||||
|
||||
`D:\ROA\ROACONTRACTE\Clase\ferestre_contracte.vc2`, `PROCEDURE do_adauga_obiectul` (`:9454+`):
|
||||
```
|
||||
:9464-9470
|
||||
Case gnParametru_prog = 1 && clienti
|
||||
lcSel = [{call pack_crm.get_politici_grup(] + Alltrim(Str(gnIdUtil)) + [,] + ;
|
||||
Alltrim(Str(goContract.id_valuta)) + [,?gnIdSucursala)}]
|
||||
... INTO Cursor crsPoliticiGrup1 ...
|
||||
```
|
||||
```
|
||||
:9486-9500
|
||||
fpp = Createobject("frm_politica_preturi") && operatorul alege O politica dintre cele la care are drept
|
||||
fpp.Show(1)
|
||||
...
|
||||
Select *, PRETFTVA As pret_unitar, 1 As cant, ... FROM cPol_pret_art With (Buffering = .T.) ;
|
||||
WHERE ales = 1 INTO Cursor cAles && articolele bifate, deja membre ale politicii alese
|
||||
```
|
||||
`pack_crm.get_politici_grup` e echivalentul ROACONTRACTE al lanțului
|
||||
`UTILIZATORI_ROL_INTERN → POLITICI_GRUPURI → CRM_POLITICI_PRETURI` folosit și de comandă/factură normală —
|
||||
operatorul primește lista politicilor lui, alege una, `cPol_pret_art` (bind pe `CRM_POLITICI_PRET_ART`
|
||||
pentru politica aleasă) afișează articolele ei, iar rândurile bifate (`ales=1`) devin linii `CTR_ARTICOLE`
|
||||
cu `id_pol_art` = `ID_POL_ART`-ul din acel rând. Din nou: **nu se alege un articol liber, se alege dintr-o
|
||||
listă deja restrânsă la politică.**
|
||||
|
||||
### B.4 — Ce se întâmplă cu `ID_POL_ART` gol la facturare
|
||||
|
||||
Vezi B.2 — spre deosebire de comandă, **nu e imposibil prin constrângere de schemă** (coloana e nullable),
|
||||
și există cazuri reale (6/27) în baza de dezvoltare. Comportamentul la facturare (dacă acea linie ajunge
|
||||
selectată) urmează același `NULL → FACT-024` ca la nomenclator — **neconfirmat direct pe o factură reală**,
|
||||
dedus din structura JOIN a cursorului (B.1).
|
||||
|
||||
---
|
||||
|
||||
## C. Ruta LISTĂ DE PREȚURI (tip 1/5/7/10/22/29/45), ca termen de comparație
|
||||
|
||||
**Corectare față de premisa din brief**: `id_pol` pe această rută **nu vine din alegerea operatorului pe
|
||||
document** pentru majoritatea tipurilor — vine automat din rolul operatorului logat, exact ca pe comandă,
|
||||
dar fără nicio interacțiune UI.
|
||||
|
||||
`ofacturare.prg:279-282` (apelul cursorului pentru tip 1,5,7,10,22,23,29):
|
||||
```
|
||||
lcSqlCursor = [{call pack_facturare.cursor_preturi(?poDate.zi_curs,?poDate.tip,?poDate.id_valuta,] + ;
|
||||
[?poDate.id_gestiune_init,?gnLuna,?gnAn,?gnIdUtil,?gnIdSucursala)}]
|
||||
```
|
||||
`poDate.id_pol` **nu e printre parametri**. `cursor_preturi` (`ff_...:2138-2354`) derivă `A.ID_POL` intern:
|
||||
```sql
|
||||
-- ff_...:2222-2251 (V_TIP IN (1,2)) / 2330-2354 identic ca structura, tot fara parametru id_pol
|
||||
FROM (select a1.id_util, a3.id_pol, ...
|
||||
from utilizatori_rol_intern a1
|
||||
left join politici_grupuri a2 on a1.id_grup = a2.id_grup
|
||||
left join crm_politici_preturi a3 on a2.id_politica = a3.id_pol
|
||||
...
|
||||
where a1.id_util = V_ID_UTIL
|
||||
and NVL(a1.ID_SUCURSALA,-99) = NVL(V_ID_SUCURSALA,-99)
|
||||
and <data curentă între valabilitatea politicii>) A
|
||||
LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL = B.ID_POL
|
||||
```
|
||||
Adică `id_pol` per linie vine din rolul de utilizator (`UTILIZATORI_ROL_INTERN.ID_UTIL = V_ID_UTIL`) și
|
||||
sucursală — **niciodată dintr-o alegere pe documentul curent**, pentru tip 1/2/5/7/10/22/29/45.
|
||||
|
||||
Câmpul de căutare vizibil pe antet există, dar e **inert pentru aceste tipuri**:
|
||||
```
|
||||
COMUN\clase\ofacturare.vc2:6822-6825
|
||||
ADD OBJECT 'Ct_clb_politici_preturi' AS ct_clb_cautare WITH ;
|
||||
cconditie = ISNULL(poDate.id_pol) or EMPTY(poDate.id_pol), ;
|
||||
cprocedura = thisform.do_cauta_politica, ...
|
||||
```
|
||||
`poDate.id_pol` e citit efectiv (trimis la Oracle) **doar** pentru `cursor_gestiune`, tip 41/23
|
||||
(`ofacturare.prg:297,777`); e validat ca obligatoriu **doar** pentru aceleași două tipuri
|
||||
(`ofacturare.vc2:7334-7337`: *„Nu ati ales politica de preturi!"*). Pe factura normală din listă de
|
||||
prețuri, câmpul poate rămâne gol fără nicio consecință — nu alimentează nimic.
|
||||
|
||||
**Concluzie C**: „lista de preț pe document" nu există ca mecanism activ pentru rutele principale — e „lista
|
||||
de preț a operatorului logat", determinată automat de rolul lui în `UTILIZATORI_ROL_INTERN`. Fiecare linie
|
||||
din `crsarticole` vine deja cu propriul `A.ID_POL` din acest join; dacă operatorul are mai multe grupuri de
|
||||
politici active simultan, teoretic același articol ar putea apărea de mai multe ori cu `id_pol` diferit —
|
||||
**neverificat pe date reale**, în afara bugetului acestei runde.
|
||||
|
||||
---
|
||||
|
||||
## Ce nu s-a putut stabili și de ce
|
||||
|
||||
- **B.4, comportamentul exact la facturare a unei linii de contract cu `ID_POL_ART` gol** — dedus din
|
||||
structura JOIN a `cursor_contract`, nu confirmat pe o factură reală emisă din contract cu o asemenea
|
||||
linie (ar necesita un contract de test cu o linie fără politică, apoi o încercare de facturare — în
|
||||
afara bugetului read-only al acestei runde).
|
||||
- **Dacă `grd_contracte` (UI) permite selecția/afișarea unei linii cu `id_pol_art` gol** — nu am urmărit
|
||||
codul de grid din `ROACONTRACTE`/`ofacturare.vc2` pentru acest caz specific.
|
||||
- **Dacă un operator cu mai multe grupuri de politici active poate primi articole duplicate cu `id_pol`
|
||||
diferit în `crsarticole`** (secțiunea C) — plauzibil din structura JOIN-ului (fără `DISTINCT` pe
|
||||
`a1.id_util`), neverificat pe date reale.
|
||||
- **Eșantionul `CTR_ARTICOLE` e mic (27 rânduri) în baza de dezvoltare** — raportul 6/27 fără
|
||||
`id_pol_art` e o dovadă reală, dar nu extrapolabilă direct la volumul de producție; merită o verificare
|
||||
separată pe o bază cu volum de producție dacă decizia depinde de frecvența cazului.
|
||||
- **Cele 37 de linii de comandă active cu articol ne-membru al politicii de pe linie** (semnalate în
|
||||
„Completare de la sesiunea principală") — cauza (import? editare ulterioară a politicii? ștergere din
|
||||
`CRM_POLITICI_PRET_ART` după ce comanda a fost creată?) nu e stabilită, și nici comportamentul real la
|
||||
facturare (`FACT-024`/`ORA-01403` așteptat pe cod, neconfirmat pe o încercare reală). E cea mai
|
||||
importantă verificare rămasă înainte de S4g — dacă garanția „membership prin construcția listei" se sparge
|
||||
și pe alte linii similare, tot raționamentul din secțiunea E (paragraful 1) trebuie recalibrat.
|
||||
- **`VANZARI.TIP = 51`** — 121 de linii fără `id_pol`, cu 179/275 rânduri `ACT` cu `SCC` populat cumulat pe
|
||||
documentele aferente — nu am identificat ce tip de document e (nu apare în `Do Case` din
|
||||
`ofacturare.prg:266-308`); posibil vânzare retail/POS sau un tip specific altui produs ROA. Neexplorat.
|
||||
- **Restul tipurilor negative (`-1`…`-13`, în afară de `-12`=ROAAUTO)** — nu au fost identificate individual;
|
||||
presupun variante storno/aviz ale rutelor deja documentate, dar nu s-a verificat pe cod care produs le
|
||||
generează.
|
||||
- **Dacă experiența lui Marius vine efectiv din contract-cu-rate, din ROAAUTO, sau din altă rută
|
||||
neidentificată (`51`, negative)** — nu s-a putut stabili fără să-l întrebăm direct; secțiunea 0d
|
||||
enumeră ipotezele plauzibile, cu dovezi pentru fiecare, dar nu poate alege între ele.
|
||||
|
||||
---
|
||||
|
||||
## Completare de la sesiunea principala (confirmare pe schema, 10.08.2026)
|
||||
|
||||
Verdictul de mai sus a fost verificat independent, pentru ca **contrazice o afirmatie a lui Marius**
|
||||
(„fac facturi pe baza de comanda care nu au politica de pret"). Confirmat:
|
||||
|
||||
- `COMENZI_ELEMENTE.ID_POL` — `nullable = N`, constrangerea `SYS_C0015376` (`"ID_POL" IS NOT NULL`) plus
|
||||
`FK_COMENZI_ELEMENTE_002`. In date: **7108 randuri total, 0 cu `ID_POL` nul, 0 cu `ID_POL` = 0**, 9
|
||||
politici distincte in uz. Pe liniile active (`STERS = 0`): 6868, tot 0 nule.
|
||||
- `CTR_ARTICOLE.ID_POL_ART` — `nullable = Y`. Confirmata asimetria semnalata in raport.
|
||||
- Toate cele 9 politici folosite pe linii de comanda au `ID_NOTA` si ajung la un `SCC`.
|
||||
|
||||
**Doua observatii noi, care nu erau in raport:**
|
||||
|
||||
1. **Politica `7 DISCOUNT` e o politica tehnica de rutare contabila, folosita in producție.** Apare pe
|
||||
**870 de linii de comanda** si trimite la nota `2 DISCOUNT` (`SCD = 667`, `SCC = 4111`). Nu e o lista
|
||||
comerciala de preturi — e o politica existenta doar ca sa duca linia la o nota contabila anume. **E
|
||||
precedentul de care are nevoie decizia 27**, si e mai apropiat de ce cere #13 decat
|
||||
`gnId_pol_pret_stoc` (care e o singura politica pentru tot stocul). Tiparul „politica tehnica per scop
|
||||
contabil" nu trebuie inventat: exista.
|
||||
2. **Garantia „apartenenta prin construcția listei" nu e etanșă in date.** Din 6868 de linii active de
|
||||
comanda, **37 au un articol care nu e membru al politicii de pe linie**
|
||||
(`NOT EXISTS (SELECT 1 FROM crm_politici_pret_art WHERE id_pol = e.id_pol AND id_articol = e.id_articol)`).
|
||||
Adica 37 de linii care, la facturare, ar trebui sa cada cu `FACT-024`. Cauza nu e stabilita — import,
|
||||
sau editarea / stergerea articolului din politica dupa introducerea comenzii. **Punctul 1 al secțiunii E
|
||||
(„nu exista cale, in UI, de a ajunge la un articol care nu e deja membru") e corect despre UI, dar nu
|
||||
despre starea datelor.** De verificat inainte de S4g: daca acele 37 de linii se factureaza fara eroare,
|
||||
exista o cale nedescoperita si intrebarea deciziei 32 se redeschide.
|
||||
|
||||
Interogarile: schema `MARIUSM_AUTO` pe `ROA_CENTRAL`, doar `SELECT`.
|
||||
179
docs/cercetare/inventar_controale_formulare.md
Normal file
179
docs/cercetare/inventar_controale_formulare.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# Inventar controale — formulare facturare (ROAFACTURARE, `COMUN\clase`)
|
||||
|
||||
Investigatie read-only (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit). Toate liniile
|
||||
sunt din fisierele text `.vc2` (FoxBin2Prg), verificate pe fisierul real (nu `.bak`). Scop: pregatirea
|
||||
unui mockup pentru un formular de facturare unificat — inventarul reflecta controalele existente, nu
|
||||
o propunere noua.
|
||||
|
||||
## 1. Butoane `frm_facturare_articole` (`ofacturare.vc2:10968-15739`)
|
||||
|
||||
Grid sursa (comanda/lista preturi) = `grd_articole` (`Left=9,Top=336,Width=326,Height=130`,
|
||||
`RecordSource=crsarticole`, `ofacturare.vc2:11570`). Grid destinatie (linii factura) = `grd_factura`
|
||||
(`Left=379,Height=337`, `RecordSource=crsfactura`, `ofacturare.vc2:12263`).
|
||||
|
||||
| Control | Clasa de baza | Caption/Picture | L/T/W/H | ToolTipText | Pozitie fata de grid | `fisier:linie` |
|
||||
|---|---|---|---|---|---|---|
|
||||
| But_modifica1 | but_modifica | (fara caption, `modific_sus.bmp`) | 773/61/30/27 | "Modificare (CTRL+M)" | dreapta-sus `grd_factura` (Anchor=9) | `ofacturare.vc2:11192` |
|
||||
| But_sterge1 | but_sterge | `sterg_sus.bmp` | 811/61/30/27 | "Stergere (CTRL+D)" | dreapta-sus `grd_factura`, langa But_modifica1 | `ofacturare.vc2:11229` |
|
||||
| But_renunt1 | but_renunt | `renunt_sus.bmp` | 792/1/30/27 | "Renuntare (ESC)" | coltul din dreapta-sus al formularului | `ofacturare.vc2:11202` |
|
||||
| But_reset1 | but_reset | `reset_sus.bmp` | 303/295/30/27 | "Reseteaza" | deasupra `grd_articole` (Top grid=336) | `ofacturare.vc2:11213` |
|
||||
| But_urmator1 | but_urmator, `caction=do_urmator` | `urmator1.bmp` | 343/348/30/27 | "Verificare" | lateral-dreapta `grd_articole` (Left grid+Width+8=343) | `ofacturare.vc2:11237` |
|
||||
| But_urmator2 | but_urmator, `caction=do_urmator2` | `urmator1.bmp` | 343/143/30/27 | "Verificare" | deasupra `grd_articole`, langa `grd_contracte` | `ofacturare.vc2:11247` |
|
||||
| **But_urmator_tot1** ("adauga tot din comanda") | but_urmator_tot, `caction=do_adauga_tot` | `urmator1_tot.bmp`, **fara ToolTipText** | 343/378/30/27, `Visible=.F.` implicit | — | lateral-dreapta `grd_articole`, sub But_urmator1 | `ofacturare.vc2:11257` |
|
||||
| But_retur | but_retur | `retur1.bmp` | 343/407/30/27, `Visible=.F.` implicit | "Retur" | lateral-dreapta `grd_articole`, sub But_urmator_tot1 | `ofacturare.vc2:11221` |
|
||||
|
||||
**"Adauga tot din comanda" — But_urmator_tot1.Click -> `do_adauga_tot`**
|
||||
(`ofacturare.vc2:13169-13198`): parcurge `crsarticole` (SCAN) si apeleaza
|
||||
`Thisform.do_adauga_articol(.T.)` pentru fiecare linie; daca articolul e gestionabil si cantitatea
|
||||
ramasa >0, cere confirmare "Nu ati selectat toata cantitatea... treceti la urmatorul?"
|
||||
(`aMessageBox` cu butoane Da/Nu/Renunta, cod 7).
|
||||
|
||||
Vizibilitate pe tip document (`Init`, `ofacturare.vc2:14976-15344`, `Do Case poDate.tip`):
|
||||
|
||||
| Control | Vizibil cand | Dovada |
|
||||
|---|---|---|
|
||||
| But_urmator_tot1 | `poDate.eProforma=1`, `poDate.lCopiere`, `tip=3` (comanda), `tip=4` (din avize), `tip in(21,28,42,47)` (aviz din comanda), `tip=25`, `tip in(8,9)` (retur factura), `tip=24` (retur aviz) | `ofacturare.vc2:15113,15120,15150,15166,15177,15215,15240,15245` |
|
||||
| But_retur | `tip in(1,5,7,10)` (facturare din lista de preturi) | `ofacturare.vc2:15127` — comentariu explicit: "pot sa fac retur de articole intr-o factura de vanzare" |
|
||||
| But_urmator2 / `grd_contracte` | eliminate (`RemoveObject`) daca nu exista `crsarticole1` (fara contracte pe formular) | `ofacturare.vc2:15294-15324` |
|
||||
|
||||
**Conventia de clase de butoane** (suita ROA): clasa de baza `buton` (`_cmd_base.vc2:16`,
|
||||
`AS _cmdbase OF "_cmd_base.vcx"`) — 30x27px implicit, `Caption=""` (buton doar cu imagine),
|
||||
`BackColor=alb`, `SpecialEffect=1`. `Click` (`_cmd_base.vc2:41-70`) executa dinamic
|
||||
`This.Parent.<caction>()` (macro pe `cAction`/`clistaparametri`) — de-asta majoritatea instantelor nu
|
||||
au `Caption`/`Click` propriu, doar `caction=<nume_metoda>`. Subclasele concrete (`but_nou`,
|
||||
`but_sterge`, `but_modifica`, `but_urmator_tot`, etc.) sunt in `cmd_butoane.vc2:7-441`, fiecare
|
||||
hardcodand `caption`/`cpictureup`/`cpicturedown`/`Picture` (`..\grafice\*.bmp`, stare sus/jos) si
|
||||
`ToolTipText` cu shortcut intre paranteze (ex. "Modificare (CTRL+M)"). `but_nou` (`do_adauga`,
|
||||
`nou_sus.bmp`, "Adaugare (CTRL+N)") — `cmd_butoane.vc2:214`.
|
||||
|
||||
## 2. Butoane `frm_facturare_articole2` (prototip, `ofacturare.vc2:15741-19355`)
|
||||
|
||||
Diferenta arhitecturala fata de #1: **un singur grid** `grd_factura` (`Left=12,Top=204,Height=240`,
|
||||
`ofacturare.vc2:16601`) cu editare inline prin comboboxuri in celule (`cCodMat.cboCodmat`,
|
||||
`cDenumire.cCboDenumire`, `cGestiune.cCboGestiune`) — nu exista casete separate de cautare articol
|
||||
(`ct_codmat`/`ct_articole`) si nici `crsarticole`/comanda sursa.
|
||||
|
||||
| Control | Clasa de baza | Caption/Picture | L/T/W/H | ToolTipText | Pozitie fata de grid | `fisier:linie` |
|
||||
|---|---|---|---|---|---|---|
|
||||
| **But_nou1** | but_nou (mostenit, `caption=do_adauga` suprascris in clasa) | `nou_sus.bmp` | 719/175/30/27 | "Adaugare (CTRL+N)" | deasupra-dreapta `grd_factura` (Top grid=204) | `ofacturare.vc2:15936` |
|
||||
| But_sterge1 | but_sterge | `sterg_sus.bmp` | 747/175/30/27 | "Stergere (CTRL+D)" | deasupra-dreapta `grd_factura`, langa But_nou1 | `ofacturare.vc2:15955` |
|
||||
| But_renunt1 | but_renunt | `renunt_sus.bmp` | 716/1/30/27 | "Renuntare (ESC)" | coltul din dreapta-sus | `ofacturare.vc2:15944` |
|
||||
|
||||
**But_nou1.Click -> `do_adauga`** (`ofacturare.vc2:17118-17122`, override propriu, nu
|
||||
`but_nou`.caction implicit): `SELECT crsFactura / APPEND BLANK / this.grd_factura.SetFocus()` —
|
||||
adauga direct un rand gol in grid si da focus, spre deosebire de `do_adauga_articol` complex din #1.
|
||||
|
||||
Nu exista `But_modifica` in aceasta clasa — editarea se face inline in celulele gridului, nu prin
|
||||
dialog separat.
|
||||
|
||||
**Bonus relevant pentru mockup**: acest prototip **incorporeaza deja aceleasi controale de antet**
|
||||
(`Ct_clb_venchelt`, `Ct_clb_sectie`, `Ct_clb_responsabil`, `Ct_clb_lucrare`, `Ct_clb_altele`,
|
||||
`Ct_clb_valuta`, `clb_fdoc`, `Clb_serie_act1`, `Clb_nract`, `Clb_dataact`, `Clb_data_scadenta`,
|
||||
`Clb_zi_curs`) direct pe formularul de articole, la `ofacturare.vc2:15741+182..749` — e cea mai
|
||||
apropiata schita existenta de un "formular unificat".
|
||||
|
||||
## 3. Antet `frm_date_factura` (`ofacturare.vc2:8482-9869`) si `frm_date_aviz` (`ofacturare.vc2:6566-7618`)
|
||||
|
||||
| Control cerut | `frm_date_factura` — caption real | `frm_date_aviz` — caption real | Tip/container | Obligatoriu / vizibil conditionat |
|
||||
|---|---|---|---|---|
|
||||
| tip venit/cheltuiala | `Ct_clb_venchelt` = "Venit / cheltuiala" | `Ct_clb_venchelt` = "Venit / cheltuiala" | `ct_clb_cautare` (container cautare) | eliminat pe aviz pentru `tip=23,41,25` (transfer/retur) — `ofacturare.vc2:7438-7461` |
|
||||
| sectie | `Ct_clb_sectie` = "Sectie" | `Ct_clb_sectie` = "Sectie" | `ct_clb_cautare` | intotdeauna prezent in ambele (nu s-a gasit `RemoveObject`) |
|
||||
| responsabil | `Ct_clb_responsabil` = "Responsabil" | `Ct_clb_responsabil` = "Responsabil" | `ct_clb_cautare` | eliminat cand `gnScadereStoc=0` sau `tip in(4,7,8,9,48,49)` — factura: `9646-9707`; aviz: eliminat pe majoritatea tipurilor cu comanda/lista |
|
||||
| lucrare | `Ct_clb_lucrare` = "Lucrare" | `Ct_clb_lucrare` = "Lucrare" | `ct_clb_cautare` | nu s-a gasit eliminare conditionata |
|
||||
| altele | `Ct_clb_altele` — label dinamic ("Altele" implicit) | idem | `ct_clb_cautare`, `.do_schimba_explicatia(...)` | eticheta se schimba pe tip: "Nr. contract"/"Nr. comanda"/"Nr. factura"/"Nr. facturi"/"Locatie" (`9633-9643`); eliminat complet daca `gnScadereStoc=0 and tip in(1,5,10)` fara copiere |
|
||||
| valuta | `Ct_clb_valuta` = "Valuta" | **nu exista pe aviz** | `ct_clb_cautare` | eliminat daca `poDate.in_valuta=0` (`ofacturare.vc2:9725-9732`) |
|
||||
| fel document | `Ct_clb_fdoc` = combo `_combobox1` FACTURA/PROFORMA/BON FISCAL, label "Tip document" | `Ct_clb_fdoc` = camp cautare "Felul documentului" (`caut_ora.vcx`) | container diferit intre cele doua forme (combo la factura, cautare la aviz) | intotdeauna vizibil |
|
||||
| serie | `Clb_serie_act` label "Serie document" | `Clb_serie_act` (fara label explicit override) | `clb_serie_act` (`serii_numere.vcx`) | eliminat daca `poDate.rezultat_serii` nu e in `(1,2,3)` — nicio serie configurata |
|
||||
| numar | `Clb_nract` = "Numar document" | `Clb_nract` = "Nr. documentului" | `clb_tx_simplu`, `InputMask=get_mask(14,0)` | mereu prezent |
|
||||
| data act | `Clb_dataact` = "Data document" | `Clb_dataact` = "Data documentului" | `clb_tx_data` | mereu prezent |
|
||||
| data scadenta | `Clb_data_scadenta` = "Data scadenta" | **nu exista pe aviz** | `clb_tx_data` | **dezactivat** (nu eliminat) cand `gnScadentaAutomata=1` (`.dezactiveaza()`, `9713-9715`) |
|
||||
| zi curs | `Clb_zi_curs` = "Data curs valutar" | `Clb_zi_curs` = "Data cursului valutar" | `clb_tx_data` | eliminat pe factura daca `tip in(8,9)` retur (`9718-9722`) |
|
||||
| client | `Ct_clb_nume_client` = "Nume client" | `Ct_clb_nume_client` = "Nume client" | `ct_clb_cautare` | eticheta se schimba pe aviz ("Retur de la"/"Gestiune sursa") in functie de tip transfer |
|
||||
|
||||
Control specific doar in `frm_date_factura`: `Ct_clb_gestiune_init`="Gestiune sursa" (`ToolTipText`:
|
||||
"Daca nu alegeti gestiunea, la scaderea din stoc a unui articol va vor fi aratate stocurile tuturor
|
||||
gestiunilor pe care aveti drepturi.") — eliminat impreuna cu `Ct_clb_responsabil` in majoritatea
|
||||
cazurilor `gnScadereStoc=0`; `txtCodFiscal`/`lblCodFiscal` (cod fiscal, `ReadOnly`, langa client) si
|
||||
`txtSoldLei`/`lblSoldLei` (sold curent client, populat din `GetSoldClient()` daca
|
||||
`poDate.id_client<>0`); `But_verifica1` (verificare ANAF). Control specific doar in
|
||||
`frm_date_aviz`: `Ct_clb_politici_preturi`="Politica de preturi" — eliminat pe majoritatea
|
||||
tipurilor cu comanda.
|
||||
|
||||
Toate conditiile de vizibilitate sunt in `Init` (nu in `do_schimba_tipdoc`, care doar realoca
|
||||
seria/numarul): `frm_date_factura.Init` = `ofacturare.vc2:9563-9796`; `frm_date_aviz.Init` =
|
||||
`ofacturare.vc2:7354-7600` (citit doar pana la ~`7533`; ultimele ~65 linii nu au fost verificate,
|
||||
vezi "Necunoscute ramase").
|
||||
|
||||
## 4. `frm_alte_date` (`ferestre_cere_date.vc2:2219-3353`)
|
||||
|
||||
| Control | Caption | Grupare logica | `fisier:linie` |
|
||||
|---|---|---|---|
|
||||
| `Ct_clb_delegat` | "Delegat" | Delegat/transport | `2553` |
|
||||
| `Ct_clb_masina` | "Masina" | Delegat/transport | `2569` |
|
||||
| `Ct_clb_agent` | "Agent" | Delegat/transport | `2537` |
|
||||
| `Clb_dataora_exp` | "Data si ora expedierii" | Delegat/transport | `2443` |
|
||||
| `opt_incasat` (radiogrup 4 optiuni) | "Fara incasare" / "Chitanta" / "Bon fiscal" / "POS Card" | Incasare | `2638` |
|
||||
| `Cb_casa` | "Casa" (-> "Banca POS" pe POS) | Incasare | `2362` |
|
||||
| `Clb_serie_chit` | "Serie chitanta" | Incasare (doar Chitanta) | `2503` |
|
||||
| `Clb_nrchit` | "Nr. chitanta" (-> "Nr. bon" pe Bon fiscal/POS) | Incasare | `2480` |
|
||||
| `Clb_incasat` | "Incasat" | Incasare | `2461` |
|
||||
| `cmdModificaBon` | (icon `but_modifica`) | Incasare (doar Bon fiscal), `caction=do_modifica_bon` | `2526` |
|
||||
| `chkPOS` | "POS" | Incasare (doar Bon fiscal) | `2409` |
|
||||
| `chkDetaliat` | "Detaliat" | Incasare (doar Bon fiscal) | `2396` |
|
||||
| `cboTipFactura` | (combo, langa label "Tip factura") | Incasare | `2378` |
|
||||
| `clb_adresa_facturare` | "Adresa facturare" | Adresa de facturare | `2422` |
|
||||
| `Ed_tx_simplu1` | "Text aditional (max. 1000 caractere)" | Text aditional | `2585` |
|
||||
| `But_modifica1` | (icon) `caction` implicit -> `do_modifica` | actiune pe delegat (deschide `nom_parteneri_modifica`) | `2343` |
|
||||
|
||||
**`actualizeaza_tipincasare`** (`2698-2856`) comuta vizibilitatea pe `This.opt_incasat.Value`:
|
||||
|
||||
| Valoare | Vizibile | Ascunse | Numar alocat |
|
||||
|---|---|---|---|
|
||||
| 1 = Fara incasare | — | `cb_casa,clb_serie_chit,clb_nrchit,clb_incasat,_shape3,lb_simplu1,cmdModificaBon,chkPOS,chkDetaliat` | dezaloca 16(chitanta)/3(bon)/26(POS) |
|
||||
| 2 = Chitanta | `cb_casa,clb_serie_chit,clb_nrchit,clb_incasat,_shape3,lb_simplu1` | `cmdModificaBon,chkPOS,chkDetaliat` | `Thisform.clb_serie_chit.genereazaNumar()` (`2783`) |
|
||||
| 3 = Bon fiscal | `cb_casa,clb_nrchit,clb_incasat,_shape3,lb_simplu1,cmdModificaBon,chkPOS,chkDetaliat` | `clb_serie_chit` | `Thisform.do_aloca_nr_bon([CLICK])` (`2807`) |
|
||||
| 4 = POS/Card | `cb_casa,clb_nrchit,clb_incasat,_shape3,lb_simplu1,cmdModificaBon` | `clb_serie_chit,chkPOS,chkDetaliat` | `Thisform.do_aloca_nr_pos([CLICK])` (`2844`) |
|
||||
|
||||
`cmdModificaBon.Click -> do_modifica_bon` (`3001-3010`) deschide `viz_config_serii_complet WITH 3`
|
||||
(`oserii_numere.prg`) si, la confirmare, dezaloca+realoca numarul de bon fiscal.
|
||||
|
||||
## 5. Butoane `frm_facturi` (lista facturi, `ofacturare_comun.vc2:1168-5126`, fisierul real —
|
||||
verificat, nu `.pre_s4butoane.bak`)
|
||||
|
||||
| Control | Caption/Picture | Metoda apelata (`caction`/override) | `fisier:linie` |
|
||||
|---|---|---|---|
|
||||
| `but_modifica1` | `modific_sus.bmp` | `caction` implicit = `inainte_de_do_modifica` -> meniu `xmenu("Modificare date factura;Editare factura (articole, cantitati, preturi)")` -> optiune 1: `do_modifica()`, optiune 2: **`do_editare_factura()`** | ADD OBJECT `1424`; `inainte_de_do_modifica` `4925-4934`; `do_modifica` `4538-4637`; `do_editare_factura` `3715-3869` |
|
||||
| `But_modifica2` | (fara caption, Top=341 — pe grid-ul de detalii, nu in bara de sus) | `caction=do_modifica_explicatie`, ToolTipText "Modificare explicatie articol" | `1434`; `do_modifica_explicatie` `4639-4656` |
|
||||
| `But_copiaza1` | `copy_sus.bmp`, `Visible=.F.` implicit | `caction` mostenit = `do_copiaza` | `1404`; `do_copiaza` `3628-3713` |
|
||||
| `But_sterge1` | `sterg_sus.bmp` | `caction` mostenit (`inainte_de_do_sterge` din clasa `but_sterge`) -> `do_sterge` | `1463`; `do_sterge` `4658-4874` |
|
||||
| `But_listare1` | `listare_sus.bmp` | `do_listare` | `1414` |
|
||||
| `But_verifica1` | (fara Picture explicit) | `do_verifica`, ToolTipText "Vericare coduri fiscale pe serverul ANAF" | `1473` |
|
||||
| `But_attach1` | `attach_sus.bmp` | `caction=` gol, override `But_attach1.Click` | `1394` |
|
||||
|
||||
**Nu exista buton separat "editare 2024"**: `do_editare_factura` este optiunea 2 din meniul popup
|
||||
deschis de `but_modifica1` (`inainte_de_do_modifica`), nu un buton propriu.
|
||||
|
||||
`Init` (`4936-4959`): daca `glLunaInchisa` (luna contabila inchisa),
|
||||
`Thisform.but_sterge1.Visible = .F.` si se scoate dreptul de stergere din `gcAcces` — singura
|
||||
conditionare de vizibilitate pe drept gasita in aceasta clasa.
|
||||
|
||||
## Necunoscute ramase
|
||||
|
||||
- `do_modifica` (frm_facturare_articole, `13746-13914`, 168 linii) si `do_sterge`/`do_editare_factura`
|
||||
(frm_facturi) nu au fost citite in detaliu — doar identificate ca existenta/semnatura; daca
|
||||
mockup-ul are nevoie de logica exacta de validare la modificare/stergere linie, trebuie citite
|
||||
explicit.
|
||||
- `frm_date_aviz.Init` (`7354-7600`) a fost citit doar pana la ~`7533`; ultimele ~65 linii (probabil
|
||||
finalizare `laPozitii`/repozitionare, simetrice cu `frm_date_factura`) nu au fost verificate.
|
||||
- Nu am verificat daca vizibilitatea controalelor din sectiunea 3 mai depinde si de **drept de
|
||||
utilizator** (nu doar `poDate.tip`/`gnScadereStoc`/`gnScadentaAutomata`) — codul citit foloseste
|
||||
doar variabile globale de setare firma si tipul documentului, nicio verificare explicita
|
||||
`gcAcces`/drepturi pe aceste containere.
|
||||
- Dimensiunile exacte (`Width`/`Height`) ale containerelor `ct_clb_cautare`/`clb_tx_*` nu sunt
|
||||
listate explicit in multe instante (mostenite din clasa de baza `caut_ora.vcx`/`lb_tx.vcx`) — nu
|
||||
am deschis acele biblioteci pentru dimensiuni implicite; doar `Left`/`Top`/`TabIndex` sunt
|
||||
suprascrise per instanta.
|
||||
- Nu am inspectat `caut_ora.vcx`/`lb_tx.vcx`/`serii_numere.vcx` (clasele de baza ale containerelor
|
||||
`clb_*`/`ct_clb_*`) pentru a confirma dimensiunile standard sau comportamentul exact al
|
||||
proprietatii `cconditie` (pare sa controleze cand campul de cautare cere obligatoriu o valoare,
|
||||
nu vizibilitatea).
|
||||
228
docs/cercetare/legatura_linie_retur.md
Normal file
228
docs/cercetare/legatura_linie_retur.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# Cercetare: legatura "linie de retur -> linie/factura originala" se persista undeva?
|
||||
|
||||
## Verdict (10 randuri)
|
||||
|
||||
**NU se persista la nivel de linie. PARTIAL la nivel de document, si numai cand a fost aleasa o
|
||||
singura factura sursa.** Cursorul Oracle care populeaza grila de retur pentru N.1 (documente tip
|
||||
8/9/24, `pack_facturare.cursor_retur_document`, `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:3949-4062`)
|
||||
**nu selecteaza deloc** `A1.ID_VANZARE` sau `A1.ID_VANZARE_DET` din `VANZARI_DETALII` — legatura cu
|
||||
factura/linia sursa se pierde chiar in query-ul care aduce datele in VFP, inainte sa existe vreo
|
||||
sansa sa fie afisata. INSERT-ul final in `VANZARI_DETALII` (`scrie_in_vanzari`, documentat deja in
|
||||
`rec_cale_vanzari_detalii.md:105-113`) nu are nicio coloana de tip sursa. Exista in schimb un link
|
||||
**la nivel de document intreg**: `VANZARI_CORESP` (`TIP=3`, `ID_VANZARE_FACT`=documentul de retur,
|
||||
`ID_VANZARE_AVIZ`=fiecare factura sursa aleasa) — dar cand utilizatorul alege mai multe facturi
|
||||
sursa deodata (selectie multipla, suportata explicit de dialog), acest link iti da **multimea** de
|
||||
facturi posibile, nu factura exacta a liniei. Pentru N.2 (`But_retur`, per articol) nu exista niciun
|
||||
document nou scris separat — returul e o linie in factura normala curenta, iar "sursa" e verificata
|
||||
doar la nivel de stoc (`RUL`/`STOC` filtrat pe `COD` legat de `VANZARI.ID_VANZARE`), nu persista ca
|
||||
atribut al liniei noi. Concluzie pentru S4f: **se livreaza fara coloana de provenienta pe linie**;
|
||||
cel mult se poate afisa, cand e un singur document sursa, "factura de retur X provine din factura Y"
|
||||
la nivel de document (din `VANZARI_CORESP`), nu per linie.
|
||||
|
||||
## 1. Ce face Oracle cu `poDate.listaid`
|
||||
|
||||
Doua cai, in functie de mecanism:
|
||||
|
||||
**N.1 (document, tip 8/9/24)**: `poDate.listaid` = CSV de `id_vanzare` (facturile alese la pasul de
|
||||
cautare multipla, `ofacturare.vc2:9200`: `poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")`).
|
||||
Ajunge la Oracle ca parametru `V_LISTAID` in `pack_facturare.cursor_retur(V_IN_VALUTA, V_LISTAID,
|
||||
V_ID_UTIL, V_CURSOR)` (`ff_...sql:3934-3947`), care e doar un wrapper peste
|
||||
`cursor_retur_document(V_IN_VALUTA, V_LISTAID, V_COPIERE=0, V_PROFORMA, V_ID_UTIL, V_CURSOR)`
|
||||
(`:3949-4062`). Acolo `V_LISTAID` devine CTE-ul `CRS` (`:3962-3964`):
|
||||
```sql
|
||||
WITH CRS AS (SELECT X as ID_VANZARE FROM table(charn2collection(V_LISTAID, V_SEPARATOR)))
|
||||
```
|
||||
folosit doar ca filtru: `WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)`
|
||||
(`:4054-4055`). **Filtru, nu persistare** — dupa acest `WHERE`, `ID_VANZARE` nu mai apare deloc in
|
||||
lista de coloane a `SELECT`-ului extern (`:3965-4028`: `ID_C` (=`ROWNUM`, sintetic), `ID_ARTICOL`,
|
||||
`LOT`, `SERIE`, `ID_POL`, preturi, `GESTIONABIL`, `CANTITATE`, `ID_GESTIUNE`, `PRET_ACHIZITIE`... dar
|
||||
nu `ID_VANZARE` si nu `ID_VANZARE_DET`). Cursorul primit de VFP in `crsarticole` nu are, deci, nicio
|
||||
coloana care sa spuna din ce factura/linie vine randul.
|
||||
|
||||
Separat, la scrierea efectiva a documentului de retur, `scrie_corespondente_vanzari(3)`
|
||||
(`:14834-14836`, apelata din `finalizeaza_factura`) foloseste tot `pack_facturare.clistaid`
|
||||
(= acelasi `poDate.listaid`, tinut in variabila de sesiune a pachetului) ca sa scrie in
|
||||
`VANZARI_CORESP` (`:15481-15516`):
|
||||
```sql
|
||||
INSERT INTO VANZARI_CORESP (ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP)
|
||||
SELECT pack_facturare.nid_vanzare, ID_VANZARE, 3
|
||||
FROM VANZARI WHERE ID_VANZARE IN (SELECT X FROM table(charn2collection(V_LISTAID,',')));
|
||||
```
|
||||
Asta scrie **cate un rand per factura sursa aleasa** (nu per linie), cu noul document de retur ca
|
||||
`ID_VANZARE_FACT` si fiecare sursa ca `ID_VANZARE_AVIZ` (numele coloanei e generic, reutilizat si
|
||||
pentru perechi aviz-factura, `TIP=1/2`, vezi `sterge_factura:5452-5457,5582-5585`).
|
||||
|
||||
**N.2 (`But_retur`, per articol)**: `poDate.listaid` = `"ID_ARTICOL:ID_VANZARE"` (una sau mai multe
|
||||
perechi CSV, `ofacturare.vc2:12888`: `thisform.cListaIdArticoleRetur = ... + STR(poArticol.id_articol) + ':' + poDate.listaid`,
|
||||
trimis la Oracle la `:13978`). In `pack_facturare` (verificat in ramura `WHEN V_CANTE < 0 and
|
||||
pack_facturare.clistaid is not null and instr(pack_facturare.clistaid, ':') > 0`,
|
||||
`ff_...sql:8142-8212`) e folosit ca filtru pentru calculul cantitatii disponibile de retur din
|
||||
rulaj (`RUL`), NU ca sa scrie o legatura:
|
||||
```sql
|
||||
AND A.COD IN (SELECT COD FROM VANZARI WHERE ID_VANZARE IN
|
||||
(SELECT id_vanzare FROM (SELECT CAST(GETWORDNUM(id_articol_id_vanzare,1,':') AS NUMBER(20,0)) id_articol,
|
||||
CAST(GETWORDNUM(id_articol_id_vanzare,2,':') AS NUMBER(20,0)) id_vanzare
|
||||
FROM (SELECT x AS id_articol_id_vanzare FROM table(cast(CHARC2COLLECTION(pack_facturare.clistaid,',') AS char_tab))))
|
||||
WHERE id_articol = V_ID_ARTICOL))
|
||||
```
|
||||
Foloseste `listaid` doar ca sa restranga `RUL` la miscarile de stoc (`A.COD`) legate de factura
|
||||
aleasa, ca sa calculeze **cantitatea inca disponibila in gestiune din acea vanzare** pentru articolul
|
||||
respectiv (`tab_stoc`, tip=1 in acel `SELECT`). Nu se scrie nicio linie noua de legatura in vreun
|
||||
tabel — e folosit exclusiv ca filtru intr-un calcul de disponibil.
|
||||
|
||||
## 2. Coloana de provenienta pe `VANZARI_DETALII`
|
||||
|
||||
Nu exista. DDL-ul complet nu e disponibil in acest repo (nu exista `CREATE TABLE VANZARI_DETALII` in
|
||||
`docs/`; confirmat deja de cercetari anterioare — `docs/cercetare/discount_verificare2.md:104-111`,
|
||||
`COMUN\docs\cercetare\rec_tva_vanzari.md:19`), dar structura efectiva a fost verificata pe schema live in
|
||||
`docs/cercetare/rec_cale_vanzari_detalii.md:157-169` (interogare `all_tab_columns`, 08.08.2026):
|
||||
35 de coloane, PK `ID_VANZARE_DET` (generat prin trigger `TRG_VANZARI_DET_BEFOINS` din
|
||||
`SEQ_VANZARI_DETALII`), FK logic `ID_VANZARE`. Lista coloanelor relevante citata acolo: `ID_ARTICOL`,
|
||||
`PRET`, `CANTITATE`, `PRET_CU_TVA`, `DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_GESTIUNE`, `CONT`,
|
||||
`ID_VALUTA`, `ID_JTVA_COLOANA`, `SERIE`, `EXPLICATIE`, `TAXCODE`, `LOT`, `STERS`, `VALIDAT`,
|
||||
`DATAORA_VALID`, `ID_UTIL_VALID`, `ID_UTILS`, `DATAORAS`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT`,
|
||||
`PRETD`, `ID_VALUTAD`, `PRETV_ORIG`, `ID_CTR`, `ID_RATA`. **Nicio coloana** de tipul
|
||||
`ID_VANZARE_SURSA`, `ID_DETALIU_SURSA`, `ID_FACT_SURSA`, `ID_VANZARE_RETUR`, `ID_ORIGINAL` sau orice
|
||||
self-referinta catre o alta linie `VANZARI_DETALII`. Cautat explicit acele siruri in tot exportul
|
||||
`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (~28000 linii) si in `COMUN\docs\` — zero potriviri
|
||||
(vezi comanda de mai jos, sectiunea "Ramas de verificat").
|
||||
|
||||
Confirmarea independenta finala si cea mai directa: INSERT-ul care trece liniile din
|
||||
`VANZARI_DETALII_TEMP` in `VANZARI_DETALII` la finalizarea oricarui document (inclusiv retur, pentru
|
||||
ca `scrie_in_vanzari` e comun tuturor tipurilor) are lista de coloane explicita
|
||||
(`rec_cale_vanzari_detalii.md:105-113`):
|
||||
```sql
|
||||
INSERT /*+ APPEND */ INTO VANZARI_DETALII
|
||||
(ID_VANZARE, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
|
||||
PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA, ID_JTVA_COLOANA)
|
||||
SELECT pack_facturare.nid_vanzare, ID_ARTICOL, ... FROM VANZARI_DETALII_TEMP WHERE ...
|
||||
```
|
||||
Nicio coloana sursa in lista. Cum `VANZARI_DETALII_TEMP` insasi e populata pentru retur din
|
||||
`cursor_retur_document` (care, cf. punctul 1, nu aduce `ID_VANZARE`/`ID_VANZARE_DET` sursa), legatura
|
||||
e pierduta cu doi pasi inainte de a ajunge la acest INSERT — nu doar "nu se scrie", ci "nu mai exista
|
||||
in date la momentul scrierii".
|
||||
|
||||
## 3. Tabel separat de legatura
|
||||
|
||||
**Da, exista, dar la nivel de document, nu de linie**: `VANZARI_CORESP(ID_VANZARE_FACT,
|
||||
ID_VANZARE_AVIZ, TIP, STERS)`. Folosit pentru mai multe perechi de corespondenta, disambiguizate prin
|
||||
`TIP`:
|
||||
- `TIP=1`: factura scrisa dintr-un aviz (`scrie_corespondente_vanzari(1)`, la facturare din aviz,
|
||||
`:14826`);
|
||||
- `TIP=2`: aviz de retur (`scrie_corespondente_vanzari(2)`, `:14830`, la `ntip=24`);
|
||||
- `TIP=3`: **factura de retur** (`scrie_corespondente_vanzari(3)`, `:14834-14836`, la `ntip in (8,9)`)
|
||||
— exact cazul cerut de S4f.
|
||||
|
||||
Verificarile de stergere din `sterge_factura` (`:5450-5494`) confirma semantica: interogheaza
|
||||
`VANZARI_CORESP WHERE ID_VANZARE_AVIZ = V_ID_VANZARE AND TIP = 3` pentru "exista facturi de retur pe
|
||||
aceasta factura?" — deci pentru o factura normala, `ID_VANZARE_AVIZ` (nume generic, refolosit) e ea
|
||||
insasi, iar `ID_VANZARE_FACT` gasit prin acea interogare e factura(le) de retur emise pe baza ei.
|
||||
Invers, pentru un document de retur dat, `SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP WHERE
|
||||
ID_VANZARE_FACT = :id_retur AND TIP = 3` da **toate** facturile sursa alese la emiterea acelui retur
|
||||
(poate fi mai multe, cf. selectie multipla din `caut_facturi_multiple_client`,
|
||||
`docs/cercetare/factura_retur_document.md:67-85`).
|
||||
|
||||
**Limita exacta**: cand pe un document de retur exista o singura factura sursa in `VANZARI_CORESP`,
|
||||
"factura originala" e determinata fara ambiguitate pentru **toate** liniile documentului de retur
|
||||
(pentru ca nu exista alt candidat). Cand exista mai multe (utilizatorul a bifat 2+ facturi la
|
||||
cautare), `VANZARI_CORESP` da multimea, dar nu se poate spune care linie de retur vine din care
|
||||
factura din multime — informatia care ar face diferenta (ID_VANZARE per linie in cursorul de
|
||||
populare) a fost deja aruncata la pasul 1/2. Nu exista niciun tabel `VANZARI_RETUR`, `RETUR_DETALII`
|
||||
sau `LEGATURI_DOCUMENTE`; cautate explicit in `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` si in
|
||||
`COMUN\docs\` — zero potriviri.
|
||||
|
||||
`DOCUMENTE.AVIZE` (coloana denormalizata pe `VANZARI`, nu tabel separat) e completata redundant tot
|
||||
din `VANZARI_CORESP` (`scrie_corespondente_vanzari:15505-15514`, `UPDATE VANZARI SET AVIZE = ...`) —
|
||||
tine text afisabil (serie+numar avize), nu un id structurat, si nu e populata pentru `TIP=3` (doar
|
||||
pentru avize, vezi apelul unic la acel `UPDATE` in corpul procedurii, comun tuturor `V_TIP`-urilor
|
||||
dar cu sens practic doar pentru avize-spre-factura).
|
||||
|
||||
## 4. Cum calculeaza serverul maximul returnabil
|
||||
|
||||
**Raspunsul e diferit pe cele doua mecanisme, si niciunul nu se sprijina pe o legatura persistata
|
||||
linie-la-linie:**
|
||||
|
||||
**N.1 (document)**: NU exista un calcul de "cat s-a mai returnat deja". Coloana pe care utilizatorul
|
||||
o vede ca "Cant. max. de returnat" (`ofacturare.vc2:15238,15243` — doar schimbare de caption pe capul
|
||||
de coloana, nu logica noua) e pur si simplu `CANTITATE` a liniei originale, asa cum vine din
|
||||
`cursor_retur_document` (`A1.CANTITATE`, `:4036`, filtrat doar pe `A1.STERS = 0`, fara nicio
|
||||
agregare cu alte retururi anterioare pe aceeasi linie). Validarea din
|
||||
`do_verifica_articol` (`:14743-14754`) nu face niciun calcul suplimentar de disponibil — blocheaza
|
||||
doar cazul `tnCantitate >= 0` cand esti in mod retur (semnul gresit), nu o depasire de maxim istoric.
|
||||
**Consecinta directa**: daca acelasi utilizator emite doua facturi de retur separate pe aceeasi
|
||||
factura sursa, a doua interogare `cursor_retur_document` va aduce din nou linia originala cu
|
||||
`CANTITATE` **intreaga**, neredusa de primul retur — nimic in cod nu scade sau marcheaza cat s-a
|
||||
returnat deja la acest nivel. Acesta e cel mai clar indiciu ca legatura linie-la-linie *nu* e tinuta
|
||||
nicaieri pentru N.1: daca ar fi fost tinuta, ar fi trebuit folosita exact aici, ca sa capeze
|
||||
cantitatea, si nu e.
|
||||
|
||||
**N.2 (`But_retur`)**: exista un calcul real de disponibil, dar e un calcul de **stoc/rulaj**, nu de
|
||||
"cat s-a returnat pe acea linie". Ramura din `pack_facturare` citata la punctul 1
|
||||
(`ff_...sql:8142-8212`) calculeaza cantitatea inca prezenta in `RUL` (miscari de stoc) care a venit
|
||||
din vanzarea aleasa (`RUL.COD` legat prin `VANZARI.COD`, filtrat pe `id_articol:id_vanzare` din
|
||||
`listaid`), plus `STOC`/`RUL_TEMP` pentru cazul general. E un calcul valid de "poti scoate din
|
||||
gestiune atat cat inca exista acolo cu provenienta asta", sprijinit pe legatura persistata
|
||||
`RUL.COD -> VANZARI.COD` (asta e adevarata "legatura care exista" pentru N.2) — dar e o legatura la
|
||||
nivelul miscarii de stoc, nu un contor "cantitate returnata" pe `VANZARI_DETALII`, si nu se
|
||||
translateaza in nicio coloana de provenienta pe linia noua scrisa.
|
||||
|
||||
## 5. Ce se poate afisa efectiv
|
||||
|
||||
- **Numarul/seria facturii originale la nivel de document de retur, cand exista o singura factura
|
||||
sursa**: SE POATE, din `VANZARI_CORESP` (`TIP=3`) join `VANZARI` pe `ID_VANZARE_AVIZ`. Interogare
|
||||
de rulat pe baza vie (nu verificata aici, doar formulata):
|
||||
```sql
|
||||
SELECT v.serie_act, v.numar_act
|
||||
FROM VANZARI_CORESP c JOIN VANZARI v ON v.ID_VANZARE = c.ID_VANZARE_AVIZ
|
||||
WHERE c.ID_VANZARE_FACT = :id_document_retur AND c.TIP = 3 AND c.STERS = 0;
|
||||
```
|
||||
Daca randul e unic, se poate afisa "provine din factura X" **pe capul documentului de retur**
|
||||
(nu pe linie — toate liniile ar arata aceeasi sursa, pentru ca e singura posibila).
|
||||
- **Cand documentul de retur are 2+ facturi sursa** (selectie multipla permisa de dialog): se poate
|
||||
afisa lista de facturi sursa posibile (tot din `VANZARI_CORESP`), dar NU care linie vine din care
|
||||
factura din lista — informatia nu exista.
|
||||
- **Numarul facturii originale pe fiecare linie individuala de retur**: NU SE POATE, in niciun caz —
|
||||
nici cand exista un singur document sursa (pentru ca "linie cu linie" nu inseamna nimic diferit de
|
||||
"documentul cu documentul" atunci, dar afirmatia stricta "aceasta linie de retur vine din linia Y a
|
||||
facturii" nu poate fi demonstrata din date, doar presupusa cand exista un singur candidat), si sigur
|
||||
nu cand exista mai multe facturi sursa sau cand linia e libera (permisa explicit de decizia 22).
|
||||
- Pentru N.2 (retur in factura normala, tip 1/5/7/10): nu exista deloc "document de retur" separat de
|
||||
arata provenienta — linia de retur e o linie normala (cu cantitate negativa) in factura curenta;
|
||||
singura urma a sursei e `RUL.COD`/`VANZARI.COD` folosita tranzitoriu la calculul de disponibil, nu
|
||||
persistata pe linia noua din `VANZARI_DETALII`.
|
||||
|
||||
## Verdict pentru plan (S4f)
|
||||
|
||||
**NU se persista legatura linie-la-linie.** Pentru documentul de retur (N.1), se poate afisa
|
||||
"factura sursa" **la nivel de document**, din `VANZARI_CORESP` (TIP=3), **doar cand exista exact o
|
||||
singura factura sursa aleasa** — cazul cu selectie multipla da doar multimea, fara atribuire per
|
||||
linie. Pe linia individuala de retur nu se poate afisa nimic verificabil din date, in niciun caz.
|
||||
Recomandare pentru S4f: se livreaza fara coloana de provenienta pe linie (cf. deciziei deja scrise in
|
||||
plan, "nu se inventeaza"); daca se vrea un gest minim, singura afisare sustenabila cu date e un text
|
||||
de tip "Retur pentru factura: X" pe **capul** documentului (deja exista, cf.
|
||||
`factura_retur_document.md:85`: `poDate.text_aditional = ... + poDate.descriere`, unde
|
||||
`poDate.descriere` e lista serie+numar a facturilor alese) — nu pe linie, si nu nou, e deja acolo.
|
||||
|
||||
## Ramas de verificat pe baza vie
|
||||
|
||||
- Nu s-a interogat live daca `VANZARI_CORESP.TIP=3` e scris consecvent pentru fiecare factura de
|
||||
retur emisa istoric (posibil sa existe date vechi scrise inainte ca aceasta ramura sa existe, sau
|
||||
prin alt cod neexaminat aici) — de rulat:
|
||||
```sql
|
||||
SELECT COUNT(*) FROM VANZARI v
|
||||
WHERE v.TIP IN (8,9) AND v.STERS = 0
|
||||
AND NOT EXISTS (SELECT 1 FROM VANZARI_CORESP c WHERE c.ID_VANZARE_FACT = v.ID_VANZARE AND c.TIP = 3);
|
||||
```
|
||||
Daca da >0, exista facturi de retur fara nicio legatura de document persistata, nici macar cea
|
||||
partiala descrisa mai sus.
|
||||
- Nu s-a verificat live cate din facturile de retur existente au >1 factura sursa in
|
||||
`VANZARI_CORESP` (adica cate din documentele reale ar cadea in cazul "multime, nu atribuire") —
|
||||
utila pentru a decide cat de des s-ar aplica de fapt afisarea propusa la punctul 5.
|
||||
- Nu s-a gasit (si nici nu era in scop) un mecanism separat pentru avize de retur custodie (`tip=50`,
|
||||
marcat "in lucru" in `tipuri_documente_facturare.md`) — daca S4f ajunge sa acopere si acel tip, de
|
||||
recercetat separat.
|
||||
- Subagentul de research auxiliar lansat pentru confirmarea independenta a definitiei
|
||||
`caut_facturi_multiple_client`/`caut_facturi_multiple_client_articol` nu a returnat un raport
|
||||
utilizabil (rezultat gol la finalizare); definitiile au fost gasite si citite direct in aceasta
|
||||
sesiune, in `COMUN\programe\oproceduri_facturare.prg:2091-2160`, deci nu blocheaza verdictul, dar
|
||||
nu a adaugat nimic peste ce e deja in acest raport.
|
||||
182
docs/cercetare/linii_comanda_articol_nemembru.md
Normal file
182
docs/cercetare/linii_comanda_articol_nemembru.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Linii de comanda cu articol nemembru al politicii de pret — verificare facturare
|
||||
|
||||
Status: FINALIZAT.
|
||||
|
||||
## Verdict
|
||||
|
||||
**DA — s-a facturat cel putin o linie fara ca FACT-024 sa se declanseze.** Comanda `497`, linia
|
||||
`4294507522` (VOUCHER DISCOUNT) / politica `7` (DISCOUNT), a fost facturata cu succes in factura
|
||||
`ID_VANZARE=1028` (serie `SSS`, numar `535`, `FACTURAT=1`, `DATA_FACTURAT=2026-03-20`), desi
|
||||
articolul **nu e membru** al politicii 7 in `CRM_POLITICI_PRET_ART` la data acestei cercetari
|
||||
(10.08.2026). **Decizia 32 se REDESCHIDE partial**: nu pentru ca ar exista o cale de cod care ocoleste
|
||||
verificarea (codul chiar cheama `contabilizeaza_articol` pentru acest tip de vanzare, cf. punctul 3),
|
||||
ci pentru ca datele arata ca membru-ul politicii **s-a schimbat dupa facturare** — vezi punctul 4.
|
||||
Pentru celelalte 34 de linii cu acelasi articol/politica (35 in total cu `CANTITATE<0`), **nu exista
|
||||
nicio factura, nici macar stearsa** — la ele fisura ramane doar o stare inconsistenta nefacturata
|
||||
(vezi punctul 1).
|
||||
|
||||
Observatie structurala separata de cea de mai sus: **inca 2 linii** (comenzile 114 si 115, alt articol,
|
||||
alta politica) au `CANTITATE>0`, adica **nefacturate inca deloc** — pentru acestea afirmatia "ar trebui
|
||||
sa cada la facturare" e neverificata pentru ca nu au fost niciodata trecute prin `contabilizeaza_articol`.
|
||||
|
||||
## 1. S-a facturat vreuna din ele fara eroare?
|
||||
|
||||
Interogare: pentru fiecare din cele 37 linii `COMENZI_ELEMENTE` cu `ID_POL NOT NULL` si articol
|
||||
nemembru in `CRM_POLITICI_PRET_ART`, am cautat linii `VANZARI_DETALII` (prin `VANZARI.ID_COMANDA`)
|
||||
cu acelasi `ID_ARTICOL`+`ID_POL`, **inclusiv cele sterse** (ca sa nu ratez o factura anulata):
|
||||
|
||||
```sql
|
||||
select ce.id_comanda_element, ce.id_comanda, ce.id_articol, ce.id_pol, ce.cantitate,
|
||||
(select count(*) from vanzari v join vanzari_detalii vd on vd.id_vanzare=v.id_vanzare
|
||||
where v.id_comanda = ce.id_comanda and vd.id_articol=ce.id_articol and vd.id_pol=ce.id_pol) as nr_total_incl_sters
|
||||
from comenzi_elemente ce
|
||||
where ce.id_pol is not null and ce.cantitate < 0
|
||||
and not exists (select 1 from crm_politici_pret_art cppa
|
||||
where cppa.id_pol = ce.id_pol and cppa.id_articol = ce.id_articol);
|
||||
```
|
||||
|
||||
Rezultat: **doar comanda 497** are potriviri (2 linii `VANZARI_DETALII`, id 1494 si 1495, ambele
|
||||
`STERS=0`, `CANTITATE=-1` fiecare, apartinand facturii `ID_VANZARE=1028`). Toate celelalte 34 de
|
||||
comenzi (349, 351, 352, 359, 360, 377, 388, 404, 412, 417, 429, 443, 447, 450, 451, 453, 455, 456,
|
||||
457, 466, 471, 475, 486, 500, 501) au **0 potriviri**, inclusiv sters.
|
||||
|
||||
**Interpretare structurala a mecanismului** (cititul codului, nu presupunere): singurul loc care
|
||||
insereaza randuri cu `CANTITATE<0` in `COMENZI_ELEMENTE` e procedura `inchide_comanda`
|
||||
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:5769-5820`):
|
||||
|
||||
```sql
|
||||
INSERT INTO COMENZI_ELEMENTE (... CANTITATE ...)
|
||||
SELECT ..., NVL(C.CANTITATE, 0) + NVL(B.CANTITATE, 0) - A.CANTITATE AS CANTITATE, ...
|
||||
FROM COMENZI_ELEMENTE A
|
||||
LEFT JOIN (... FROM VANZARI_DETALII_TEMP ...) B ON ... -- lotul curent de facturare
|
||||
LEFT JOIN (... FROM VANZARI JOIN VANZARI_DETALII ...) C ON ... -- deja facturat anterior
|
||||
WHERE A.ID_COMANDA = :comanda
|
||||
AND SIGN(A.CANTITATE) * A.CANTITATE >
|
||||
SIGN(A.CANTITATE) * (NVL(C.CANTITATE, 0) + NVL(B.CANTITATE, 0));
|
||||
```
|
||||
|
||||
Asta insemna ca randul negativ **nu dovedeste singur ca linia a fost facturata** — el reprezinta
|
||||
*diferenta ramasa* fata de cantitatea comandata, la momentul **inchiderii** comenzii, chiar daca
|
||||
`B` (lotul curent) si `C` (facturat anterior) sunt amandoua 0. O comanda inchisa fara sa i se
|
||||
factureze o anumita linie tot primeste un rand `CANTITATE = -A.CANTITATE`. De-asta 34 din 35 de
|
||||
linii "negative" nu au nicio factura in spate: comenzile respective au fost **inchise** (probabil
|
||||
manual, ca "restanta anulata"), nu facturate pe acea linie. **Zero cazuri in date pentru acestea nu
|
||||
demonstreaza ca nu se poate factura — demonstreaza doar ca nu s-a intamplat.**
|
||||
|
||||
Pentru comanda 497 insa, potrivirea cu 2 linii reale, active, `FACTURAT=1` in `VANZARI_DETALII`
|
||||
e dovada directa (nu inferenta) ca acea combinatie articol/politica **a ajuns** intr-o factura emisa.
|
||||
|
||||
## 2. Reconfirmarea cifrei si lista liniilor
|
||||
|
||||
Cifra **37** se reconfirma exact (nu 6868/nemembru cum sugera masuratoarea anterioara — aceea era
|
||||
alt numitor; numitorul corect pentru linii cu `ID_POL NOT NULL` e **7108**, nu 6868):
|
||||
|
||||
```sql
|
||||
select count(*) from comenzi_elemente ce where ce.id_pol is not null; -- 7108
|
||||
select count(*) from comenzi_elemente ce where ce.id_pol is not null
|
||||
and not exists (select 1 from crm_politici_pret_art cppa
|
||||
where cppa.id_pol = ce.id_pol and cppa.id_articol = ce.id_articol); -- 37
|
||||
```
|
||||
|
||||
Compozitie (verificata, `select sign(cantitate), count(*) ... group by sign(cantitate)`):
|
||||
- **35 linii** cu `CANTITATE=-1`: acelasi articol/politica in toate — `ID_ARTICOL=4294507522`
|
||||
("VOUCHER DISCOUNT"), `ID_POL=7` ("DISCOUNT"). Comenzi: 349(x2), 351, 352(x2), 359, 360(x2), 377,
|
||||
388, 404, 412(x2), 417, 429, 443, 447, 450, 451, 453, 455, 456, 457, 466, 471(x2), 475(x2),
|
||||
486(x2), 497(x2), 500, 501(x2).
|
||||
- **2 linii** cu `CANTITATE=+10` (nefacturate inca, nicio urma de consum):
|
||||
- comanda 114, `ID_ARTICOL=4294507508` ("CAFEA TEST - PRODUS TEST PENTRU IMPORT WEB"), `ID_POL=2`
|
||||
("LISTA 2"), data comanda 2025-09-10, `COMENZI.STERS` neverificat suplimentar dar randul e
|
||||
prezent activ.
|
||||
- comanda 115, `ID_ARTICOL=4171144217` ("CAFEA 1"), `ID_POL=2`. **Anomalie separata**: `COMENZI`
|
||||
nu contine niciun rand cu `ID_COMANDA=115` — antetul comenzii lipseste, doar linia de element a
|
||||
supravietuit. NEDETERMINABIL DIN DATE de ce (nu exista audit/istoric pe `COMENZI`); posibil
|
||||
date de test/import, dat fiind ca articolul insusi e "CAFEA TEST ... PENTRU IMPORT WEB" pe
|
||||
cealalta linie similara.
|
||||
|
||||
Toate cele 37 de linii au `COMENZI_ELEMENTE.STERS=0` (nicio linie de comanda marcata stearsa).
|
||||
|
||||
Stare de facturare per linie: singura cu urma reala de facturare e comanda 497 (vezi punctul 1);
|
||||
restul de 36 sunt **nefacturate** pe aceasta combinatie articol/politica.
|
||||
|
||||
## 3. Verificare cod FACT-024 (`contabilizeaza_articol`)
|
||||
|
||||
Blocul citat in brief (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7278-7302`) e identic cu ce ruleaza
|
||||
in productie — verificat direct in codul compilat, nu doar in scriptul istoric (fisierul `.sql` e un
|
||||
istoric cumulativ cu **doua definitii** ale `contabilizeaza_articol` in text, la linia 746 si la
|
||||
7173; doar a doua e cea vie):
|
||||
|
||||
```sql
|
||||
select owner, line from all_source
|
||||
where name='PACK_FACTURARE' and type='PACKAGE BODY'
|
||||
and text like '%FACT-024%';
|
||||
```
|
||||
confirma acelasi text de eroare in schema `MARIUSM_AUTO` (Dev).
|
||||
|
||||
Verificarea foloseste view-ul `VCRM_POLITICI_PRET_ART`:
|
||||
```sql
|
||||
SELECT COMPUS, ID_POL_ART INTO ... FROM VCRM_POLITICI_PRET_ART
|
||||
WHERE ID_ARTICOL = detalii_articol.id_articol AND ID_POL = detalii_articol.id_pol;
|
||||
EXCEPTION WHEN NO_DATA_FOUND THEN ... RAISE_APPLICATION_ERROR(-20000, ... '(FACT-024)');
|
||||
```
|
||||
Am verificat textul view-ului (`user_views`): `VCRM_POLITICI_PRET_ART` are `CRM_POLITICI_PRET_ART PA`
|
||||
ca tabel-sursa (driving table) al tuturor `LEFT JOIN`-urilor, fara niciun `WHERE` care sa filtreze
|
||||
`PA` (nu exista coloana `STERS` pe `CRM_POLITICI_PRET_ART`). Deci view-ul are exact acelasi set de
|
||||
perechi `(ID_POL, ID_ARTICOL)` ca tabelul de baza — verificarea mea prin `NOT EXISTS` pe
|
||||
`CRM_POLITICI_PRET_ART` e echivalenta cu ce evalueaza codul. **Concluzie: pentru toate cele 37 de
|
||||
linii, `SELECT ... INTO` ar da `NO_DATA_FOUND` -> `FACT-024`, daca ar trece prin
|
||||
`contabilizeaza_articol` ACUM, cu starea curenta a `CRM_POLITICI_PRET_ART`.**
|
||||
|
||||
Apelul e neconditionat pentru facturi standard: am cautat toate apelurile
|
||||
`pack_facturare.contabilizeaza_articol(tab_detalii(i))` in codul **compilat** (`all_source`, schema
|
||||
`MARIUSM_AUTO`) — 6 aparitii, linii 4877, 4901, 5594, 5618, 5876, 5900. Linia 4901 e in procedura
|
||||
`scrie_factura2` (facturarea principala), in ramura `ELSE` a unui `CASE pack_facturare.ntip` care
|
||||
exclude doar transferurile intre subunitati (23,25,30,41), avizele de custodie (42,47) si facturile
|
||||
cu rate (2,6,52 cu `id_rata<>0`) — **tip 3 (tipul facturii 1028) cade in ELSE, deci
|
||||
`contabilizeaza_articol` chiar s-a executat pentru acea linie.**
|
||||
|
||||
Prin urmare, pentru comanda 497: fie (a) la data facturarii (20.03.2026) articolul 4294507522 CHIAR
|
||||
era membru al politicii 7 si a fost scos ulterior din `CRM_POLITICI_PRET_ART`, fie (b) verificarea a
|
||||
fost ocolita altfel. Nu am gasit dovada de tip (b) — vezi punctul 4.
|
||||
|
||||
## 4. Cauza
|
||||
|
||||
`CRM_POLITICI_PRET_ART` **nu are coloana `STERS`** si nu exista niciun tabel de istoric/audit pentru
|
||||
ea (`select table_name from user_tables where table_name like '%POLITICI%' or ... '%AUDIT%'` — doar
|
||||
tabelele de configurare curenta, niciunul de istoric). Stergerile din aceasta tabela sunt deci
|
||||
**fizice, fara urma**. Nu pot reconstitui direct daca perechea (politica 7, articol 4294507522) a
|
||||
existat la 20.03.2026.
|
||||
|
||||
Coloanele de audit disponibile pe randurile facturii/comenzii **nu arata nicio editare ulterioara**:
|
||||
`VANZARI.ID_UTILS`/`DATAORAS` si `VANZARI_DETALII.ID_UTILS`/`DATAORAS` (1494, 1495) sunt toate NULL —
|
||||
niciun `UPDATE` standard nu a atins aceste randuri dupa creare. Deci nu exista dovada ca linia de
|
||||
factura ar fi fost modificata ulterior prin fluxul nou de "editare factura emisa" (adaugat recent in
|
||||
proiect, cf. changelog 2.11.15) — desi asta nu exclude o editare care ar fi ocolit acele coloane de
|
||||
audit, doar ca nu am gasit dovada ei.
|
||||
|
||||
Indiciu indirect, dar nu concludent: articolul 4294507522 e denumit **"VOUCHER DISCOUNT"**, iar
|
||||
politica 7 e **"DISCOUNT"** — o pereche generica folosita ca linie de discount pe **35 de comenzi
|
||||
diferite**, nu un caz izolat. Reutilizarea masiva a exact aceleiasi perechi sustine ipoteza unei
|
||||
**modificari de catalog** (articolul-voucher scos ulterior din lista politicii DISCOUNT ca decizie de
|
||||
business/curatare), mai degraba decat 35 de erori de introducere independente. **Aceasta ramane insa
|
||||
o inferenta din tipar, nu o dovada directa — se marcheaza NEDETERMINABIL DIN DATE strict pe cauza si
|
||||
data exacta a schimbarii.**
|
||||
|
||||
## Ce am incercat si ce a esuat
|
||||
|
||||
- Doua incercari de a delega cautarea codului sursa VFP/PL-SQL unui subagent `Explore` au esuat cu
|
||||
raspunsuri goale/neinformative ("Nimic nou.", "No new input to act on.") — fara nicio dovada ca ar
|
||||
fi rulat vreo cautare. Am renuntat la delegare si am facut cautarile direct cu Grep si Read.
|
||||
- Interogarea `all_source` fara `owner=` a intors linii duplicate/interclasate (schema `ACN` are de
|
||||
asemenea un `PACK_FACTURARE`) — a trebuit filtrat explicit pe `owner='MARIUSM_AUTO'` pentru
|
||||
rezultate coerente.
|
||||
- Un `prompt '---text---'` intre doua instructiuni SQL in acelasi fisier `.sql` a produs iesire
|
||||
amestecata (headerul s-a lipit de urmatoarea instructiune) — am renuntat la `prompt` si am rulat
|
||||
interogarile in fisiere separate.
|
||||
|
||||
## Date tehnice folosite
|
||||
|
||||
- Conexiune: `MARIUSM_AUTO/ROMFASTSOFT@ROA_CENTRAL` (Dev), via
|
||||
`D:\ROA\instantclient_19_18\sqlplus.exe`, fisiere `.sql` ASCII in scratchpad, niciun `INSERT`/`UPDATE`/`DDL` rulat.
|
||||
- Cod verificat: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(text istoric cumulativ) si pachetul compilat `MARIUSM_AUTO.PACK_FACTURARE` (`all_source`, sursa de
|
||||
adevar pentru ce ruleaza efectiv).
|
||||
124
docs/cercetare/modifica_date_factura_parametri.md
Normal file
124
docs/cercetare/modifica_date_factura_parametri.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Cercetare: `pack_facturare.modifica_date_factura` — parametri, mapare pe coloane si pe controale
|
||||
|
||||
Sursa pachetului: `D:\ROA\ROAACNPRO\PACK_FACTURARE.pck` (declaratie spec `:921-935`, corp `:14392-14462`).
|
||||
Apel VFP: `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vc2`, metoda `frm_facturi.do_modifica` (`:4538-4637`),
|
||||
apelul efectiv la `:4599-4614`. Formular de editare: `frm_modifica_factura` (`:5262-5774`).
|
||||
|
||||
Nu exista alt fisier `.pck`/`.sql` cu semnatura diferita pentru `modifica_date_factura` in
|
||||
`D:\ROA\ROAFACTURARE` sau `COMUN`; niciun alt loc din VFP (in afara de `ofacturare_comun.vc2` si
|
||||
copia identica `.bak`) nu apeleaza procedura — cautat cu grep pe `*.vc2/*.sc2/*.prg` in tot arborele.
|
||||
|
||||
## 1-2. Tabel parametri (semnatura + destinatie in Oracle)
|
||||
|
||||
| # | Parametru | Tip (`.pck:921-935`) | Coloana / tabel scrise in corp (`.pck:14392-14462`) | Observatii |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `V_ID_VANZARE` | `NUMBER` | Folosit doar in `WHERE ID_VANZARE = V_ID_VANZARE` (`:14424`) si ca sursa pentru `lnIdFact` (`:14426`) | Identitate randului, niciodata scris |
|
||||
| 2 | `V_ID_RUTA` | `NUMBER` | `VANZARI.ID_RUTA` (`:14414`) | Scris neconditionat |
|
||||
| 3 | `V_ID_DELEGAT` | `NUMBER` | `VANZARI.ID_DELEGAT` (`:14415`) | Scris neconditionat |
|
||||
| 4 | `V_ID_AGENT` | `NUMBER` | `VANZARI.ID_AGENT` (`:14416`) | Scris neconditionat |
|
||||
| 5 | `V_ID_MASINA` | `NUMBER` | `VANZARI.ID_MASINA` (`:14417`) | Scris neconditionat |
|
||||
| 6 | `V_DATAORA_EXP` | `DATE` | `VANZARI.DATAORA_EXP` (`:14418`) | Scris neconditionat |
|
||||
| 7 | `V_ID_FACTURARE` | `VANZARI.ID_FACTURARE%TYPE` | `VANZARI.ID_FACTURARE` (`:14419`) | Scris neconditionat |
|
||||
| 8 | `V_LISTARE_DETALIATA` | `VANZARI.LISTARE_DETALIATA%TYPE` | `VANZARI.LISTARE_DETALIATA = NVL(V_LISTARE_DETALIATA, 0)` (`:14420`) | NVL la 0 |
|
||||
| 9 | `V_TEXT_ADITIONAL` | `VANZARI.TEXT_ADITIONAL%TYPE` | `VANZARI.TEXT_ADITIONAL` (`:14421`) | Scris neconditionat, fara NVL |
|
||||
| 10 | `V_TIP_SAFT` | `VANZARI.TIP_SAFT%TYPE DEFAULT NULL` | `VANZARI.TIP_SAFT` (`:14422`) | Scris neconditionat |
|
||||
| 11 | `V_EFACTURA` | `VANZARI.EFACTURA%TYPE DEFAULT NULL` | `VANZARI.EFACTURA` (`:14423`) | Scris neconditionat |
|
||||
| 12 | `V_DATA_ACT` | `VANZARI.DATA_ACT%TYPE DEFAULT NULL` | `VANZARI.DATA_ACT` (`:14448`) + `DOCUMENTE.DATAACT`, `ACT.DATAACT`, `IREG_PARTENERI.DATAACT`, `JV2007.DATAACT`, `RUL.DATAACT` si `RUL.DATAOUT` (`:14449-14453`), toate `WHERE ID_FACT = lnIdFact` | Doar daca `V_DATA_ACT IS NOT NULL AND V_DATA_ACT <> NVL(ldDataAct, SYSDATE)` (`:14447`) |
|
||||
| 13 | `V_DATA_SCAD` | `VANZARI.DATA_SCAD%TYPE DEFAULT NULL` | `VANZARI.DATA_SCAD` (`:14457`) + `ACT.DATASCAD`, `IREG_PARTENERI.DATASCAD` (`:14458-14459`), `WHERE ID_FACT = lnIdFact` | Doar daca `V_DATA_SCAD IS NOT NULL AND V_DATA_SCAD <> NVL(ldDataScad, sysdate)` (`:14456`) |
|
||||
| 14 | `V_NUMAR_ACT` | `VANZARI.NUMAR_ACT%TYPE DEFAULT NULL` | `VANZARI.NUMAR_ACT` (`:14439`) + `DOCUMENTE.NRACT`, `ACT.NRACT`, `IREG_PARTENERI.NRACT`, `JV2007.NRACT`, `RUL.NRACT` (`:14440-14444`), `WHERE ID_FACT = lnIdFact` | Doar daca `V_NUMAR_ACT IS NOT NULL AND V_NUMAR_ACT <> NVL(lnNrAct, 0)` (`:14438`) |
|
||||
| 15 | `V_SERIE_ACT` | `VANZARI.SERIE_ACT%TYPE DEFAULT NULL` | `VANZARI.SERIE_ACT` (`:14429`) + `DOCUMENTE.SERIE_ACT`, `ACT.SERIE_ACT`, `IREG_PARTENERI.SERIE_ACT`, `JV2007.SERIE_ACT`, `RUL.SERIE_ACT` (`:14430-14434`), `WHERE ID_FACT = lnIdFact` | Doar daca `V_SERIE_ACT IS NOT NULL AND V_SERIE_ACT <> NVL(lcSerieAct, '')` (`:14428`) |
|
||||
|
||||
Parametrii 2-11 se scriu neconditionat in `VANZARI` la fiecare apel (chiar cu `NULL`, daca asa vine
|
||||
argumentul). Parametrii 12-15 (`data_act`, `data_scad`, `numar_act`, `serie_act`) au tratament
|
||||
conditionat: se propaga si in `DOCUMENTE`/`ACT`/`IREG_PARTENERI`/`JV2007`/`RUL` (unde e cazul),
|
||||
filtrate dupa `ID_FACT` (nu dupa `ID_VANZARE`) — deci ating toate randurile din acele tabele legate
|
||||
de acelasi document, nu doar randul `VANZARI` curent. Se scriu doar daca valoarea trimisa e
|
||||
`NOT NULL` si difera de valoarea curenta.
|
||||
|
||||
## 3. Apelul din VFP
|
||||
|
||||
`COMUN\clase\ofacturare_comun.vc2:4599-4614`, in interiorul unui `SCAN` peste `crsfacturi` (una sau
|
||||
mai multe inregistrari alese):
|
||||
|
||||
```
|
||||
begin pack_facturare.modifica_date_factura(<<Alltrim(Str(poRec.id_vanzare))>>,
|
||||
<<Alltrim(Nvl(Str(poRec.id_ruta),[NULL]))>>,
|
||||
<<Alltrim(Nvl(Str(poRec.id_delegat),[NULL]))>>,
|
||||
<<Alltrim(Nvl(Str(poRec.id_agent),[NULL]))>>,
|
||||
<<Alltrim(Nvl(Str(poRec.id_masina),[NULL]))>>,
|
||||
to_date('<<TTOC(poRec.dataora_exp,1)>>','YYYYMMDDHH24:MI:SS'),
|
||||
<<Alltrim(Nvl(Str(poRec.id_facturare),[NULL]))>>,
|
||||
<<ALLTRIM(STR(NVL(poRec.listare_detaliata,0)))>>,
|
||||
?poRec.text_aditional,
|
||||
?poRec.tip_saft,
|
||||
?poRec.efactura,
|
||||
?poRec.data_act,
|
||||
?poRec.data_scad,
|
||||
?poRec.numar_act,
|
||||
?poRec.serie_act);
|
||||
end;
|
||||
```
|
||||
|
||||
Toate argumentele vin din structura `poRec`, populata printr-un `Scatter Name poRec Memo` din
|
||||
cursorul `crsfacturi` la intrarea in `do_modifica` (`:4538-4581`), apoi editata prin
|
||||
`frm_modifica_factura` cat timp e afisat (`:4588-4590`), inainte de executia `SCAN`-ului.
|
||||
|
||||
Observatie de comportament: cand sunt alese mai multe inregistrari (`lnNrInreg > 1`), ramura
|
||||
`Otherwise` (`:4565-4580`) face `Scatter Name poRec Memo Blank` si reseteaza explicit
|
||||
`id_ruta`, `id_delegat`, `id_agent`, `id_masina`, `dataora_exp`, `id_facturare`,
|
||||
`listare_detaliata`, `tip_saft`, `text_aditional`, `efactura` la valori goale/`NULL`/`0` —
|
||||
campurile `data_act`/`data_scad`/`numar_act`/`serie_act` raman goale prin acelasi `NVL`. Daca
|
||||
formularul nu le modifica explicit, `SCAN`-ul trimite aceste valori goale la **fiecare** inregistrare
|
||||
aleasa (parametrii 2-11 se scriu neconditionat in corpul procedurii — vezi tabelul de mai sus).
|
||||
|
||||
## 4. Mapare parametru -> control -> fereastra
|
||||
|
||||
| Parametru | Control | Fereastra | Observatii |
|
||||
|---|---|---|---|
|
||||
| `V_ID_VANZARE` | — | — | Nu are control; e identitatea randului din `crsfacturi` (`poRec.id_vanzare`) |
|
||||
| `V_ID_RUTA` | `Ct_clb_ruta` (`ControlSource` prin `cprocedura=thisform.do_cauta_ruta`, seteaza `poRec.id_ruta`) | `frm_modifica_factura` (`:5510-5526`, cautare `:5695-5721`) | |
|
||||
| `V_ID_DELEGAT` | `Ct_clb_delegat` (`do_cauta_delegat` seteaza `poRec.id_delegat`) | `frm_modifica_factura` (`:5473-5489`, cautare `:5654-5678`) | |
|
||||
| `V_ID_AGENT` | `Ct_clb_agent` (`do_cauta_agent` seteaza `poRec.id_agent`) | `frm_modifica_factura` (`:5455-5471`, cautare `:5639-5652`) | |
|
||||
| `V_ID_MASINA` | `Ct_clb_masina` (`do_cauta_masina` seteaza `poRec.id_masina`) | `frm_modifica_factura` (`:5491-5508`, cautare `:5680-5693`) | |
|
||||
| `V_DATAORA_EXP` | `Clb_dataora_exp.Text_simplu1` (`ControlSource="poRec.dataora_exp"`) | `frm_modifica_factura` (`:5437-5453`) | validat obligatoriu in `inainte_de_do_termin` (`:5723-5734`) |
|
||||
| `V_ID_FACTURARE` | `clb_adresa_facturare` (`do_cauta_adresa` seteaza `poRec.id_facturare` + `poRec.adresa_facturare`) | `frm_modifica_factura` (`:5418-5435`, cautare `:5621-5637`) | |
|
||||
| `V_LISTARE_DETALIATA` | `chkDetaliat` (`ControlSource="poRec.listare_detaliata"`) | `frm_modifica_factura` (`:5379-5390`) | |
|
||||
| `V_TEXT_ADITIONAL` | `Ed_tx_simplu1._EDBASE1` (`ControlSource="poRec.text_aditional"`, `MaxLength=1000`) | `frm_modifica_factura` (`:5528-5551`) | comentariul VFP la `:4575` spune "MAXIM 250 CARACTERE", dar `MaxLength` control e 1000 — contradictie neverificata mai departe |
|
||||
| `V_TIP_SAFT` | `cboTipFactura` (`ControlSource="poRec.tip_saft"`, `RowSource` din `saft_tip_facturi`) | `frm_modifica_factura` (`:5335-5351`) | `Visible = m.gl406` (`:5739-5741`), deci controlul e ascuns cand `gl406` e fals (`gl406` definit in `COMUN\programe\oinit_optiuni.prg:524`) |
|
||||
| `V_EFACTURA` | **niciun control** | — | `poRec.efactura` vine doar din `Scatter` initial pe `crsfacturi` (cazurile `lnNrInreg=0/1`, `:4538-4564`) sau e fortat `0` la selectie multipla (`:4576`); formularul `frm_modifica_factura` nu are niciun obiect legat de `poRec.efactura` — nu poate fi schimbat de utilizator din acest formular azi |
|
||||
| `V_DATA_ACT` | `txtDataAct` (`ControlSource="poRec.data_act"`) | `frm_modifica_factura` (`:5572-5581`) | vezi blocaj la punctul 5 |
|
||||
| `V_DATA_SCAD` | `txtDataScad` (`ControlSource="poRec.data_scad"`) | `frm_modifica_factura` (`:5583-5592`) | vezi blocaj la punctul 5 |
|
||||
| `V_NUMAR_ACT` | `txtNrAct` (`ControlSource="poRec.numar_act"`) | `frm_modifica_factura` (`:5594-5603`) | vezi blocaj la punctul 5 |
|
||||
| `V_SERIE_ACT` | `txtSerieAct` (`ControlSource="poRec.serie_act"`) | `frm_modifica_factura` (`:5605-5614`) | vezi blocaj la punctul 5 |
|
||||
|
||||
Nu exista niciun parametru mapat pe `frm_alte_date` (`COMUN\clase\ferestre_cere_date.vc2:2219`) —
|
||||
clasa aceea nu e implicata deloc in fluxul `do_modifica`/`modifica_date_factura`.
|
||||
|
||||
## 5. Parametri blocati/needitabili azi in `frm_modifica_factura`
|
||||
|
||||
Patru checkbox-uri controleaza accesul la seria/numarul/datele actului, si patru textbox-uri asociate:
|
||||
|
||||
- `chkSerieAct`, `chkNrAct`, `chkDataAct`, `chkDataScad`: definite cu `Enabled = .F.`
|
||||
(`:5405-5416`, `:5392-5403`, `:5353-5364`, `:5366-5377`). Devin `Enabled` doar in `PROCEDURE Init`
|
||||
(`:5743-5746`):
|
||||
```
|
||||
this.chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))
|
||||
this.chkNrAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))
|
||||
this.chkDataAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))
|
||||
this.chkDataScad.Enabled = !EMPTY(NVL(poRec.numar_act,0))
|
||||
```
|
||||
adica doar daca factura are deja un numar de act completat (`poRec.numar_act` nenul) la
|
||||
deschiderea formularului. Pe o factura fara act (proforma/nefinalizata), toate patru raman
|
||||
needitabile.
|
||||
- `txtSerieAct`, `txtNrAct`, `txtDataAct`, `txtDataScad`: definite cu `Enabled = .F.`
|
||||
(`:5605-5614`, `:5594-5603`, `:5572-5581`, `:5583-5592`) si se activeaza doar din evenimentul
|
||||
checkbox-ului corespunzator — `chkSerieAct.Valid` (`:5770-5772`), `chkNrAct.Valid`
|
||||
(`:5766-5768`), `chkDataAct.Valid` (`:5758-5760`), `chkDataScad.Click` (`:5762-5764`) — deci
|
||||
necesita ca checkbox-ul insusi sa fie deja `Enabled` (conditia de mai sus).
|
||||
- `V_EFACTURA`: fara control — needitabil in acest formular indiferent de stare (punctul 4).
|
||||
- `V_ID_VANZARE`: nu e un camp de editat, e identitatea randului.
|
||||
|
||||
Toti ceilalti parametri (`id_ruta`, `id_delegat`, `id_agent`, `id_masina`, `dataora_exp`,
|
||||
`id_facturare`, `listare_detaliata`, `text_aditional`, `tip_saft`) au controale mereu `Enabled`
|
||||
(fara restrictie in cod), `tip_saft` fiind conditionat doar de vizibilitate (`gl406`), nu de
|
||||
`Enabled`.
|
||||
224
docs/cercetare/nota_contabila_fara_politica.md
Normal file
224
docs/cercetare/nota_contabila_fara_politica.md
Normal file
@@ -0,0 +1,224 @@
|
||||
# Nota contabila pentru articol fara politica de pret (proiect #13)
|
||||
|
||||
Cercetare pe cod, fara modificari. Continua raportul anterior
|
||||
`cont_venit_corespondente.md` (SCC vine prin lantul
|
||||
`CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI.ID_NOTA -> CRM_NOTE_VANZARI.ID_SET -> NOTE_CONTABILE`,
|
||||
citit de `cursor_articol` din `pack_facturare.contabilizeaza_articol`).
|
||||
|
||||
Sursa: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql`
|
||||
(pachet `pack_facturare`, 17020 linii) si `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg` +
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql` (pachet `pack_auto`).
|
||||
|
||||
---
|
||||
|
||||
## 1. Ce face `contabilizeaza_articol` cand `id_pol` e NULL/0
|
||||
|
||||
Raspuns: **ridica exceptia FACT-024 si intreruce functia, inainte sa ajunga la cursorul care
|
||||
citeste SCC**. Nu exista o ramura de default si nu se scrie nota cu SCC NULL pentru acest caz.
|
||||
|
||||
Corpul complet e la `ff_...:7182-7556`. Primul lucru pe care il face (inainte de `cursor_articol`,
|
||||
inainte de orice `scrie_nota`) e:
|
||||
|
||||
```
|
||||
ff_...:7286-7311
|
||||
BEGIN
|
||||
SELECT COMPUS, ID_POL_ART
|
||||
INTO V_COMPUS, V_ID_POL_ART
|
||||
FROM VCRM_POLITICI_PRET_ART
|
||||
WHERE ID_ARTICOL = detalii_articol.id_articol
|
||||
AND ID_POL = detalii_articol.id_pol;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
SELECT DENUMIRE INTO lcArticol FROM NOM_ARTICOLE WHERE id_articol = detalii_articol.id_articol;
|
||||
SELECT NUME_LISTA_PRETURI INTO lcPolitica FROM CRM_POLITICI_PRETURI WHERE id_pol = detalii_articol.id_pol;
|
||||
RAISE_APPLICATION_ERROR(-20000,
|
||||
'Articolul ' || detalii_articol.id_articol || '|' || lcArticol ||
|
||||
' nu este definit in politica de preturi ' ||
|
||||
detalii_articol.id_pol || '|' || lcPolitica || '! (FACT-024)');
|
||||
END;
|
||||
```
|
||||
|
||||
`ID_POL = detalii_articol.id_pol` cu `id_pol` NULL nu poate potrivi niciun rand in SQL standard
|
||||
(`X = NULL` e necunoscut, nu adevarat) — deci pentru `id_pol` NULL sau 0 (presupunand ca nu exista
|
||||
o politica cu `ID_POL = 0`), `SELECT INTO` intra mereu pe `NO_DATA_FOUND`.
|
||||
|
||||
**Observatie suplimentara, neceruta explicit dar relevanta**: in ramura de exceptie, al doilea
|
||||
`SELECT ... INTO lcPolitica FROM CRM_POLITICI_PRETURI WHERE id_pol = detalii_articol.id_pol` sufera
|
||||
de aceeasi problema — cu `id_pol` NULL, si acest `SELECT INTO` da tot `NO_DATA_FOUND`, care **nu e
|
||||
tratat** in acest bloc (nu exista un al doilea handler). Deci mesajul FACT-024 "curat" (cu numele
|
||||
politicii) nu s-ar afisa niciodata pentru cazul `id_pol IS NULL` — s-ar propaga o exceptie
|
||||
`NO_DATA_FOUND` neprinsa din interiorul handler-ului, cu alt mesaj Oracle generic, nu FACT-024 cu
|
||||
text. Codul articolului (`lcArticol`) apuca sa fie rezolvat inaintea acestui eșec, deci
|
||||
identificarea articolului nu s-ar pierde in log/eroare, dar textul final ar fi ORA-01403, nu
|
||||
mesajul FACT-024 formatat. Pentru `id_pol = 0` (nu NULL), daca exista vreo politica cu id 0, acel
|
||||
`SELECT` ar reusi si mesajul FACT-024 ar iesi corect.
|
||||
|
||||
Cursorul `cursor_articol` (care citeste SCD/ASCD/SCC/ASCC din `NOTE_CONTABILE` prin lant) **nu se
|
||||
mai deschide** — funcția a ridicat deja exceptia mai sus. Deci intrebarea "cursorul intoarce zero
|
||||
randuri, ce face codul mai departe" nu se pune in acest caz: nu ajunge acolo.
|
||||
|
||||
Concluzie Q1: **orice linie de vanzare cu `id_pol` NULL/0 trimisa la `contabilizeaza_articol` in
|
||||
starea actuala a codului arunca o eroare si opreste tranzactia** (nu scrie nota cu cont NULL, nu
|
||||
pune default). Cerinta proiectului #13 ("articol din nomenclator fara politica de pret, pret
|
||||
tastat manual, pe orice tip de document") ar lovi FACT-024 (sau exceptia neprinsa descrisa mai sus)
|
||||
daca linia respectiva e trecuta prin acest cod neschimbat.
|
||||
|
||||
---
|
||||
|
||||
## 2. Validare la `scrie_nota` pentru cont NULL
|
||||
|
||||
Raspuns: **nu exista**. Corpul complet e la `ff_...:12338-12570`.
|
||||
|
||||
- Singura verificare care ar fi atins asta e comentata:
|
||||
```
|
||||
ff_...:12441-12453
|
||||
/* IF V_SUMA IS NULL THEN
|
||||
RAISE_APPLICATION_ERROR(...) ...
|
||||
END IF;*/
|
||||
```
|
||||
Si oricum verifica `V_SUMA` (suma calculata), nu `V_SCD`/`V_SCC`.
|
||||
- `INSERT INTO ACT_TEMP (... SCD, ASCD, SCC, ASCC ...)` (`ff_...:12458-12544`) insereaza direct
|
||||
`V_SCD`/`V_ASCD`/`V_SCC`/`V_ASCC` primite ca parametri, fara niciun `NVL`/`CASE`/verificare de
|
||||
NULL pe ele.
|
||||
- Nu exista `NOT NULL` verificat in cod pentru aceste coloane in acest fisier (constrangerea ar fi
|
||||
la nivel de DDL al tabelei `ACT`/`ACT_TEMP`, care nu face parte din pachetul PL/SQL cercetat —
|
||||
vezi sectiunea Neverificat).
|
||||
|
||||
Deci daca cineva ar ocoli blocul de la punctul 1 (de ex. printr-un `id_pol` care *exista* in
|
||||
`CRM_POLITICI_PRET_ART` dar al carui lant `CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI -> NOTE_CONTABILE`
|
||||
e incomplet, asa incat `D.SCC` iese NULL din LEFT JOIN), `scrie_nota` ar scrie nota cu `SCC = NULL`
|
||||
fara sa protesteze. Asta e insa un scenariu diferit de "fara politica de pret" (aici politica exista,
|
||||
dar lantul de sub ea e incomplet) — nu e cazul cerut de proiectul #13 asa cum a fost formulat.
|
||||
|
||||
---
|
||||
|
||||
## 3. Toti apelantii lui `contabilizeaza_articol` in fisier
|
||||
|
||||
Cautare exhaustiva `Grep 'contabilizeaza_articol'` pe tot fisierul: 3 apeluri (in afara de propria
|
||||
definitie si un comentariu):
|
||||
|
||||
| Linie | Procedura apelanta | Domeniu / flux |
|
||||
|---|---|---|
|
||||
| `ff_...:6150` | `scrie_factura2` (corp `ff_...:6029-6272`) | **Fluxul principal de scriere** — pentru orice document ale carui randuri nu cad in ramurile speciale ale `CASE`-ului de la `ff_...:6081-6151`: transfer intre subunitati (`ntip IN (23,25,30,41)`), factura/aviz custodie (`ntip IN (42,47)`), rata cu `id_rata<>0` (`ntip IN (2,6,52)`). Toate celelalte tipuri — **factura normala** (`ntip<=20`), hotel, restaurant, nota de plata, vanzare retail, tipurile 48/49/51/52, aviz simplu, aviz catre clienti debitori, si **aviz retur (tip 24)** cand e scris prin `scrie_factura2` — ajung la `contabilizeaza_articol` aici. Aceasta e apelarea "de baza" pe care se sprijina interpretarea corpului functiei facuta la punctul 1 (ramurile `CASE` din interiorul `contabilizeaza_articol`, `ff_...:7407-7432`, mapeaza direct pe tipurile de document tratate de acest apelant). |
|
||||
| `ff_...:6867` | `scrie_factura_avize_retur` (corp `ff_...:6273-6666`) | Flux **factura scrisa pe baza de avize + retur simultan**. Apelul e in interiorul buclei pe `articole_aviz` (`ff_...:6841-6960` zona), pentru randurile cu `custodie = 1` (marfa in custodie ce se descarca din avize). |
|
||||
| `ff_...:7149` | `scrie_aviz_retur` (corp `ff_...:7094-7180`) | Procedura dedicata **aviz retur** (`V_ID_DELEGAT, V_ID_MASINA, ...`). Apelul e in ramura `ELSE` a testului `pack_facturare.nfactavizcust = 1` (`ff_...:7124-7150`) — deci pentru randurile care **nu** sunt in custodie. |
|
||||
|
||||
Concluzie Q3: **da**, `contabilizeaza_articol` e apelata pe fluxul principal de facturare
|
||||
(`scrie_factura2`, care acopera factura normala si majoritatea celorlalte tipuri de document), nu
|
||||
doar din `scrie_aviz_retur`. Raportul anterior lasase asta neverificat; e confirmat aici cu cele 3
|
||||
puncte de apel gasite si citate mai sus — nu exista un al 4-lea apel in fisier.
|
||||
|
||||
---
|
||||
|
||||
## 4. Precedentul ROAAUTO "Alte servicii" — **nu e de fapt un precedent pentru cazul cerut**
|
||||
|
||||
Asta contrazice premisa din cerere. Firul (`crsalteserv -> crsvanztemp -> adauga_articol_factura_deviz`)
|
||||
duce spre un flux care **ocoleste complet `contabilizeaza_articol`**, deci nu demonstreaza ce se
|
||||
intampla in `pack_facturare` pentru un articol fara politica — demonstreaza doar ca exista un *alt*
|
||||
mecanism de contabilizare, in afara pachetului `pack_facturare`.
|
||||
|
||||
Pasii, cu citate:
|
||||
|
||||
1. **`crsalteserv -> crsvanztemp`** (`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1040-1041`):
|
||||
```
|
||||
Insert Into (lcCursorDeviz)(id_articol,denumire,explicatie,cantitate,um,id_jtva_coloana,proc_Tvav,taxcode,pret,discount_unitar) ;
|
||||
Select id_articol,denumire,"",cantitate,um,CAST(lnIdJTvaColoana as N(6) null) as id_jtva_coloana,CAST(lnProcTvav as N(7,2) null) as proc_tvav,CAST(lnTaxCode as N(6) null) as taxcode,pretftva,0 From crsalteserv Where !Deleted()
|
||||
```
|
||||
Nu exista coloana `id_pol` in `crsalteserv`, in `lcCursorDeviz`, sau in cursorul `crsvanztemp`
|
||||
creat la `oproceduri_devize.prg:1190-1192` (schema explicita a cursorului nu include `id_pol`).
|
||||
|
||||
2. **`crsvanztemp -> adauga_articol_factura_deviz`** (`oproceduri_devize.prg:1240-1257`): apelul RPC
|
||||
trimite `id_articol, explicatie, serie, pret_achizitie, pretd, id_valuta_d, pret, id_valuta,
|
||||
curs, multiplicator, proc_tvav, id_jtva_coloana, cantitate, discount_unitar, id_gestiune, cont,
|
||||
pret_cu_tva, NULL, NULL, taxcode` — **fara `id_pol`**, pentru ca procedura insasi nu are un
|
||||
parametru `id_pol`.
|
||||
|
||||
3. **Semnatura `adauga_articol_factura_deviz`** (spec `ff_...:468-488`, corp `ff_...:4675-4745`):
|
||||
nu exista `V_ID_POL IN NUMBER` printre parametri. `INSERT INTO VANZARI_DETALII_TEMP` la
|
||||
`ff_...:4697-4744` **nu include coloana `ID_POL`** in lista de coloane inserate — deci
|
||||
`ID_POL` ramane pe valoarea implicita a coloanei (probabil NULL; DDL-ul tabelei nu face parte
|
||||
din pachetul cercetat, vezi Neverificat). **Asta confirma partea (a) a intrebarii: da, liniile
|
||||
"alte servicii" ajung cu `id_pol` gol** in `VANZARI_DETALII_TEMP`.
|
||||
|
||||
4. **Dar acele randuri nu trec niciodata prin `contabilizeaza_articol`.** Apelantul din
|
||||
`oproceduri_devize.prg` nu cheama `scrie_factura2` / `scrie_factura_avize` / `scrie_aviz_retur`
|
||||
(singurele proceduri care cheama `contabilizeaza_articol`, vezi punctul 3). In schimb cheama, in
|
||||
ordine (`oproceduri_devize.prg:1211-1310`):
|
||||
- `pack_facturare.initializeaza_date_factura(...)`
|
||||
- bucla `pack_facturare.adauga_articol_factura_deviz(...)` (doar insert in `VANZARI_DETALII_TEMP`)
|
||||
- opțional `pack_facturare.scrie_incasari(...)` (incasare, nu venit din vanzare)
|
||||
- `pack_facturare.scrie_in_vanzari(0, ...)` (`ff_...:13497-13962`)
|
||||
- `pack_auto.actualizeaza_deviz(...)` (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`)
|
||||
|
||||
**`scrie_in_vanzari` nu scrie nici o nota contabila de venit.** Corpul complet
|
||||
(`ff_...:13497-13962`) face: `INSERT INTO VANZARI` (antetul documentului), `scrie_cursuri`,
|
||||
`scrie_seturi`, apoi
|
||||
```
|
||||
ff_...:13714-13766
|
||||
INSERT /*+ APPEND */ INTO VANZARI_DETALII (..., ID_POL, ...)
|
||||
SELECT ..., ID_POL, ... FROM VANZARI_DETALII_TEMP;
|
||||
```
|
||||
(copiaza `ID_POL` — care e NULL pentru aceste randuri — direct in `VANZARI_DETALII`, fara nicio
|
||||
validare), si la final actualizeaza totalurile cache pe `VANZARI`. **Nu apare niciun apel catre
|
||||
`contabilizeaza_articol`, `scrie_nota`, `cumuleaza_note_act` sau `finalizeaza_factura` in acest
|
||||
corp** (verificat citind procedura cap-coada).
|
||||
|
||||
`pack_auto.actualizeaza_deviz` (`ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`) face doar:
|
||||
```
|
||||
SELECT MAX(ID_FACT) INTO lnIdFact FROM ACT WHERE COD = pack_contafin.get_cod() AND SCD NOT LIKE '5%';
|
||||
UPDATE DEV_ORDL SET PROC_TVAV = ... WHERE ID_ORDL IN (...);
|
||||
UPDATE RUL SET ID_FACT = lnIdFact WHERE ...;
|
||||
UPDATE NOM_LUCRARI SET ID_FACT = lnIdFact WHERE ... (doar pt anumite id_set);
|
||||
```
|
||||
Adica **presupune ca exista deja un rand in `ACT`** (nu `ACT_TEMP`) cu `SCD NOT LIKE '5%'` pentru
|
||||
codul documentului curent, si doar propaga `ID_FACT` gasit acolo spre `DEV_ORDL`/`RUL`/`NOM_LUCRARI`.
|
||||
Nu insereaza el insusi in `ACT` sau `NOTE_CONTABILE`, si nu contine nicio referinta la `SCC`,
|
||||
`NOTE_CONTABILE` sau `CRM_POLITICI*` (cautat explicit, zero rezultate in acest fisier).
|
||||
|
||||
**Concluzie Q4, corectand premisa din cerere**: liniile "Alte servicii" chiar ajung cu `id_pol` gol
|
||||
(confirmat), dar **nu demonstreaza ca `contabilizeaza_articol` tolereaza `id_pol` gol** — pentru ca
|
||||
nu trec niciodata prin el. Ele demonstreaza doar ca exista deja, pentru fluxul deviz ROAAUTO, o cale
|
||||
de facturare paralela (`scrie_in_vanzari` + `pack_auto.actualizeaza_deviz`) care **nu genereaza deloc
|
||||
nota contabila de venit prin `pack_facturare`**. Daca acele randuri capata totusi un cont de venit in
|
||||
`ACT`/`NOTE_CONTABILE` in productie, mecanismul nu e vizibil in codul cercetat (posibil un
|
||||
trigger pe tabel, un job batch, sau o scriere manuala/alt modul neexaminat — vezi Neverificat).
|
||||
**Nu se poate folosi acest precedent ca raspuns de fapt la intrebarea 1.**
|
||||
|
||||
---
|
||||
|
||||
## 5. Vreun mecanism care sa foloseasca `NOM_ARTICOLE.CONT` drept cont de venit
|
||||
|
||||
Raspuns: **NU**, in `pack_facturare`.
|
||||
|
||||
Cautat explicit `Grep -i 'NOM_ARTICOLE\.CONT'` pe intregul fisier
|
||||
`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` (17020 linii) — **zero potriviri**. Singurele utilizari
|
||||
ale `NOM_ARTICOLE` gasite in `contabilizeaza_articol` insusi sunt pentru `DENUMIRE` (mesajul de
|
||||
eroare FACT-024, `ff_...:7295-7298`), nu pentru `CONT`.
|
||||
|
||||
`crs_rand_articol.scc` (folosit ca `V_SCC` la `ff_...:7434`) vine exclusiv din
|
||||
`D.SCC` din join-ul `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI -> NOTE_CONTABILE`
|
||||
(`ff_...:7261, 7275-7276`) — nu exista ramura alternativa care sa substituie `NOM_ARTICOLE.CONT`
|
||||
cand politica lipseste. (Raportul anterior confirmase deja separat ca `NOM_ARTICOLE.CONT` e folosit
|
||||
doar ca **cont de gestiune**, transmis catre `descarca_gestiune`, `ff_...:7499`.)
|
||||
|
||||
---
|
||||
|
||||
## Neverificat
|
||||
|
||||
- **Continutul efectiv al `NOTE_CONTABILE`/`CRM_POLITICI_PRET_ART`** (daca exista politici cu
|
||||
`ID_POL = 0`, ce SCC au liniile din productie pentru documente "alte servicii" existente) —
|
||||
necesita interogare pe baza de date, nu doar cod.
|
||||
- **Constrangerile DDL** ale `VANZARI_DETALII_TEMP.ID_POL` si `ACT_TEMP.SCC`/`ACT.SCC` (NOT NULL?
|
||||
default?) — scripturile DDL ale acestor tabele nu au fost cautate/citite in aceasta cercetare
|
||||
(am cautat doar in pachetele PL/SQL `ff_...COMUN_PACK_FACTURARE.sql` si `..._AUTO_PACK_AUTO.sql`).
|
||||
- **Cine scrie efectiv contul de venit (`ACT`/`NOTE_CONTABILE`) pentru documentele "alte servicii"
|
||||
din ROAAUTO**, daca se scrie deloc. `pack_auto.actualizeaza_deviz` presupune ca exista deja un
|
||||
rand `ACT` cu `SCD NOT LIKE '5%'` pentru codul documentului curent — sursa acelui rand nu a fost
|
||||
gasita in `oproceduri_devize.prg` sau in corpul `scrie_in_vanzari`/`actualizeaza_deviz` cercetate.
|
||||
Ar trebui cautat: alte proceduri `pack_auto` (fisierul `ff_2026_03_31_02_AUTO_PACK_AUTO.sql` are
|
||||
si alte proceduri necercetate aici), triggere pe `VANZARI`/`VANZARI_DETALII`/`ACT`, sau apeluri
|
||||
din alte forme VFP (`.scx`/`.vcx`) din ROAAUTO care preced acest apel RPC. Nu am cautat exhaustiv
|
||||
triggere in afara folderului `SCRIPTURI_CLAR\2026` (cautare limitata, zero rezultate acolo).
|
||||
- **Ce se intampla exact daca `id_pol = 0` si exista o politica reala cu `ID_POL = 0`** (caz
|
||||
teoretic, neexclus explicit din schema) — ar schimba concluzia de la "eroare neprinsa" la
|
||||
"FACT-024 cu mesaj complet", dar tot ramane o eroare, nu un default silentios.
|
||||
373
docs/cercetare/optiune_firma_cont_debit.md
Normal file
373
docs/cercetare/optiune_firma_cont_debit.md
Normal file
@@ -0,0 +1,373 @@
|
||||
# Optiune de firma pentru contul de debit (`SCD`) pe ramura fara politica de pret (#13, decizia 36)
|
||||
|
||||
Cercetare read-only, terminata. Continua `canal_cont_venit_fara_politica.md` (proiectarea
|
||||
parametrului de cont, sectiunea "Proiectarea parametrului de cont contabil", `:27-241`) si
|
||||
verificarea ei, `parametru_cont_contabilizeaza_articol.md`. Acolo, randul `SCD` din tabelul de
|
||||
sectiunea 3 propunea **`SCD` hardcodat `'4111'`**, marcat explicit "de confirmat cu Marius"
|
||||
(`canal_cont_venit_fara_politica.md:146`). **Decizia 36 (Marius, 10.08.2026): `SCD` vine dintr-o
|
||||
optiune de firma, cu `4111` ca implicit** — motivul fiind ca se poate schimba la client fara
|
||||
recompilare. Aici e proiectarea acelei optiuni. Decizia 31 se aplica: datele din baza de dev arata
|
||||
ce *exista*, nu ce e configurat la client — nu se generalizeaza pe numere, doar pe tipar/structura.
|
||||
|
||||
Oracle interogat doar `SELECT`, schema `MARIUSM_AUTO`/`ROA_CENTRAL`. Niciun fisier de cod atins.
|
||||
|
||||
---
|
||||
|
||||
## 0. Rezumat, in cinci randuri
|
||||
|
||||
Mecanismul de optiuni de firma **exista deja de 15+ ani** (`PACK_SESIUNE.getoptiunefirma`, tabelul
|
||||
`OPTIUNI`, 458 randuri azi) si are **exact tiparul cerut deja in productie, in acelasi pachet**:
|
||||
`pack_facturare.scrie_incasare2` seteaza `V_SCD` dintr-o optiune (`RF_CONT_INCASARE_BONFISCAL` etc.,
|
||||
cate una per tip de incasare), cu fallback pe un cont hardcodat daca optiunea lipseste — **acelasi
|
||||
camp (`SCD`), acelasi pachet, aceeasi sursa Oracle** (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`,
|
||||
liniile 13191-13233, tratate integral in sectiunea 2). Ecranul de editare (`frm_optiuni`/
|
||||
`frm_optiuni_nou`, `COMUN\clase\oOptiuni.vc2`) e **complet generic** peste tabelul `OPTIUNI` —
|
||||
adaugarea cheii noi **nu cere niciun cod VFP nou** in ecranul de optiuni, doar randul in tabel.
|
||||
Propunerea (sectiunea 3): cheia noua `FACT_SCD_ARTFPRET` (sau numele ales de Marius), instalata cu
|
||||
valoare implicita `'4111'` chiar in randul din `OPTIUNI` (tiparul deja folosit de toate exemplele
|
||||
gasite), **plus** fallback hardcodat `'4111'` in cod langa `getoptiunefirma`, in oglinda exacta a
|
||||
tiparului `scrie_incasare2` — dubla plasa de siguranta (instalare veche fara randul din `OPTIUNI`
|
||||
**si** rand editat gresit/gol raman amandoua acoperite).
|
||||
|
||||
---
|
||||
|
||||
## 1. Cum functioneaza mecanismul de optiuni de firma, azi
|
||||
|
||||
### 1.1 Stocare — tabelul `OPTIUNI`
|
||||
|
||||
Interogat direct (`ALL_TAB_COLUMNS`, schema curenta), 458 randuri azi:
|
||||
|
||||
| Coloana | Tip | Null | Rol |
|
||||
|---|---|---|---|
|
||||
| `ID_OPTIUNI` | `NUMBER` | N | cheie surogat, PK implicita |
|
||||
| `VARNAME` | `VARCHAR2(30)` | Y | cheia optiunii — cautata `UPPER(TRIM(...))`, deci case-insensitive in practica |
|
||||
| `VARTYPE` | `VARCHAR2(10)` | Y | eticheta de tip pentru randare/parsare in VFP: `CHARACTER`/`NUMERIC`/`CURRENCY`/`DATE`/`DATETIME`/`LOGICAL` — **descriptiva, nu impusa de Oracle**; distinct azi: `CHARACTER` 156, `NUMERIC` 288, `LOGICAL` 11, `DATE` 3 |
|
||||
| `VARVALUE` | `VARCHAR2(1000)` | Y | valoarea, **mereu text**, indiferent de `VARTYPE` |
|
||||
| `VARVALUE2` | `CLOB` | Y | valoare alternativa, folosita de un mecanism separat (`citeste_optiune(tcOptiune, tlValue2)`, `oinit_optiuni.prg:802-823`) — irelevant aici |
|
||||
| `VARDESC` | `VARCHAR2(100)` | Y | descriere afisata in ecran |
|
||||
| `PROGRAM` | `VARCHAR2(30)` | Y | produsul care a creat optiunea (metadata) |
|
||||
| `PROGRAME` | `VARCHAR2(1000)` | Y | lista CSV de produse pentru care optiunea e vizibila/relevanta (folosita la filtrare in incarcarea globala VFP, sectiunea 1.4) |
|
||||
| `ID_PRG_OWNER` | `NUMBER` | Y | neexaminat mai departe, neрелevant aici |
|
||||
|
||||
**Fara `ID_FIRMA`.** Motivul: fiecare firma are **schema Oracle proprie** — tabelul `OPTIUNI` din
|
||||
schema curenta de conexiune **este** deja "optiunile firmei curente"; nu exista randare
|
||||
multi-firma in acelasi tabel. Confirmat si de codul VFP: `frm_optiuni.cschema` e setat la
|
||||
constanta de compilare `CONTAFIN_ORACLE` (`oOptiuni.vc2:1089`, acelasi simbol folosit peste tot in
|
||||
COMUN pentru "schema firmei curente"), iar `getoptiunefirma(tcS, tcOptiune)` **primeste** un
|
||||
parametru de schema (`tcS`, azi mereu `USER`) dar **nu il foloseste in interogare** — `select
|
||||
varvalue from optiuni` fara calificare de schema (cod exact la sectiunea 1.2) — pentru ca sesiunea
|
||||
Oracle e deja conectata direct in schema firmei. `tcS` ramane doar din compatibilitate istorica cu
|
||||
supraincarcarea `getOptiuneFirma(tcOptiune)` (fara schema), care apeleaza pe cea cu doi parametri
|
||||
cu `user` (sectiunea 1.2).
|
||||
|
||||
### 1.2 Semnatura si contract — `PACK_SESIUNE.getoptiunefirma`
|
||||
|
||||
Sursa curenta confirmata prin `ALL_SOURCE` pe pachetul live (`PACK_SESIUNE`, tip `PACKAGE BODY`) —
|
||||
identica, verificat, cu ultima versiune gasita in `SCRIPTURI_CLAR`
|
||||
(`2014/04/ff_2014_04_24_01_COMUN_PACK_SESIUNE.sql:328-350`; nicio modificare de atunci, cautare
|
||||
`FUNCTION getoptiunefirma` in `SCRIPTURI_CLAR/2024`, `/2025`, `/2026` — zero rezultate):
|
||||
|
||||
```sql
|
||||
FUNCTION getoptiunefirma(tcS IN VARCHAR2, tcOptiune IN VARCHAR2)
|
||||
RETURN VARCHAR2 IS
|
||||
lcValue Optiuni.Varvalue%Type := '';
|
||||
BEGIN
|
||||
BEGIN
|
||||
select varvalue
|
||||
INTO lcValue
|
||||
from optiuni
|
||||
where UPPER(TRIM(VARNAME)) = UPPER(TRIM(tcOptiune));
|
||||
EXCEPTION
|
||||
WHEN OTHERS THEN
|
||||
lcValue := '';
|
||||
END;
|
||||
RETURN lcValue;
|
||||
END;
|
||||
|
||||
FUNCTION getOptiuneFirma(tcOptiune IN VARCHAR2) RETURN VARCHAR2 IS
|
||||
lcValue Optiuni.Varvalue%Type := '';
|
||||
BEGIN
|
||||
lcValue := getOptiuneFirma(user, tcOptiune);
|
||||
return lcValue;
|
||||
END;
|
||||
```
|
||||
|
||||
Contract, verificat pe `RETURN`, nu dedus din nume:
|
||||
|
||||
- **Parametru**: numele optiunii (`tcOptiune`), cautat `UPPER(TRIM(...))` — insensibil la caz si la
|
||||
spatii; a doua forma (`getOptiuneFirma(tcOptiune)`, un singur parametru) e cea folosita azi peste
|
||||
tot in `pack_facturare` (vezi sectiunea 2) — deleaga la forma cu doi parametri cu `user`.
|
||||
- **Cand optiunea NU exista** (`NO_DATA_FOUND`, prins de `WHEN OTHERS` generic): **`RETURN ''`**
|
||||
(string gol), **niciodata `NULL` explicit si niciodata exceptie propagata**. Insa in Oracle un
|
||||
`VARCHAR2` egal cu `''` **este** `NULL` la nivel de reprezentare (Oracle nu distinge stringul gol
|
||||
de `NULL`) — deci orice apelant care testeaza `IF V_X IS NULL THEN` dupa un apel la
|
||||
`getoptiunefirma` **prinde si cazul "optiune inexistenta"**, fara cod suplimentar. Acesta e
|
||||
exact tiparul folosit de toti apelantii gasiti (sectiunea 2).
|
||||
- **`WHEN OTHERS THEN lcValue := ''`** inghite **orice** eroare, nu doar `NO_DATA_FOUND` (de ex. si
|
||||
`TOO_MANY_ROWS` daca ar exista vreodata doua randuri cu acelasi `VARNAME` — nu exista constrangere
|
||||
`UNIQUE` pe `VARNAME`, doar convenția aplicatiei). Nu e un risc nou introdus de propunerea de
|
||||
fata — e comportamentul mecanismului de 15+ ani, pastrat ca atare.
|
||||
- **Variante**: **o singura functie**, intotdeauna `VARCHAR2`. **Nu exista** `getoptiunefirma`
|
||||
numerica/booleana separata in Oracle — conversia se face **la apelant**, in PL/SQL cu
|
||||
`TO_NUMBER(NVL(...))` (exemplu confirmat, acelasi fisier, `scrie_incasare2`,
|
||||
`ff_...:13177`: `TO_NUMBER(NVL(pack_sesiune.getoptiunefirma('D406'), '0'))`) sau in VFP cu
|
||||
`VAL()`/`CTOD()`/etc., pe baza coloanei `VARTYPE` (vezi `oinit_optiuni.prg:179-211`, functia
|
||||
`actualizeaza_optiuni_program`, care materializeaza fiecare optiune intr-o variabila globala VFP
|
||||
cu prefix dupa tip: `gc<nume>` pentru `CHARACTER`, `gn<nume>` pentru `NUMERIC`,
|
||||
`gl<nume>` pentru `LOGICAL` etc.). **Pentru un cont contabil (`VARCHAR2(4)`), varianta corecta e
|
||||
cea de baza, `VARTYPE='CHARACTER'`** — exact tipul folosit de toate exemplele cu cont din
|
||||
sectiunea 2, fara nicio conversie necesara la citire in PL/SQL (`V_SCD` e deja `VARCHAR2`).
|
||||
|
||||
### 1.3 Ecranul de editare — complet generic peste tabel
|
||||
|
||||
`COMUN\clase\oOptiuni.vc2`, doua clase relevante:
|
||||
|
||||
- **`frm_optiuni`** (`:1064-1314`) — grid pe view-ul VFP local `v_optiuni` (populat dintr-un
|
||||
`SELECT * FROM <schema>.optiuni`, vezi `oinit_optiuni.prg:337` in `viz_optiuni`), 4 coloane
|
||||
(`varname`, `vartype`, `varvalue`, `vardesc`), cu cautare doar dupa `varname LIKE`
|
||||
(`do_cauta`, `:1261-1278`) si butoane Adauga/Modifica/Sterge care deschid `frm_optiuni_nou`.
|
||||
- **`frm_optiuni_nou`** (`:1316-1462`) — formular Adauga/Modifica cu 4 campuri: `varname` (text,
|
||||
`Format="!K"` = uppercase), `vartype` (combobox cu lista fixa `CHARACTER,CURRENCY,NUMERIC,
|
||||
DATETIME,DATE,LOGICAL`), `varvalue` (text), `vardesc` (text). Validare la salvare
|
||||
(`inainte_de_do_termin`, `:1417-1450`): **doar "nu e gol"** pe `varname`, `vartype`, `varvalue` —
|
||||
**nicio validare de format sau de continut** (nu verifica lungime, nu verifica daca `varvalue`
|
||||
e un cont existent in planul de conturi, nimic specific per optiune).
|
||||
- **`cus_odata_optiuni`** (`:139-206`) — clasa de persistenta: `INSERT`/`UPDATE`/`DELETE` generic pe
|
||||
`optiuni (varname, vartype, varvalue, vardesc)` — confirma ca **orice cheie noua** functioneaza
|
||||
fara nicio schimbare la acest cod; scrie exact cele 4 coloane editabile din ecran.
|
||||
|
||||
**Concluzie la intrebarea "adaugarea unei optiuni noi cere cod nou in ecran": NU.** Ecranul e
|
||||
generic peste tabel dupa cele 4 coloane editabile. Randul nou (`FACT_SCD_ARTFPRET`, sectiunea 3)
|
||||
apare automat in grid dupa ce e inserat, si e editabil din prima fara nicio modificare VFP.
|
||||
|
||||
*Nota conexa, nu necesara pentru mecanismul Oracle*: la fiecare pornire de sesiune, VFP
|
||||
materializeaza **toate** randurile din `OPTIUNI` in variabile globale (`optiuni_firma`,
|
||||
`oinit_optiuni.prg:234-310`), filtrate `Isnull(programe) Or gcNumeProgram $ programe` — deci coloana
|
||||
`PROGRAME` conteaza doar pentru **vizibilitatea in acest mecanism global VFP**, nu pentru
|
||||
`getoptiunefirma` (care nu se uita deloc la `PROGRAME`). Optiunea noua nu are nevoie de acest canal
|
||||
VFP (Oracle o citeste direct), dar completarea corecta a `PROGRAME` evita zgomot/confuzie daca
|
||||
cineva se uita in `frm_optiuni` din ROAFACTURARE si se asteapta sa vada optiunea listata acolo cu
|
||||
`gcNumeProgram = 'ROAFACTURARE'` in `PROGRAME`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Exemple reale de optiuni existente, cu lantul complet
|
||||
|
||||
### 2a. `RF_CONT_INCASARE_*` — exact tiparul cerut, in acelasi pachet, pe acelasi camp `SCD`
|
||||
|
||||
**Cel mai relevant exemplu posibil**: patru optiuni care alimenteaza direct `SCD` pe o nota
|
||||
contabila, in `pack_facturare.scrie_incasare2` — **aceeasi functie Oracle pe care propunerea de la
|
||||
`canal_cont_venit_fara_politica.md` o modifica** (acelasi fisier,
|
||||
`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`).
|
||||
|
||||
**Randul din tabel** (interogat direct, 4 randuri, VARTYPE `CHARACTER` la toate):
|
||||
|
||||
| VARNAME | VARVALUE azi | VARDESC | PROGRAM | PROGRAME |
|
||||
|---|---|---|---|---|
|
||||
| `RF_CONT_INCASARE_BONFISCAL` | `5311` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5311 = 4111` | `ROAFACTURARE` | `ROAFACTURARE,ROAACNPRO,ROACOMENZI,ROACONTRACTE,ROAGEST,ROARESTAURANT` |
|
||||
| `RF_CONT_INCASARE_CARDBANCAR` | `5125` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5113 = 4111` | idem | idem |
|
||||
| `RF_CONT_INCASARE_TICHETE` | `5323` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5323 = 4111` | idem | idem |
|
||||
| `RF_CONT_INCASARE_CHITANTA` | `5311` | `CONTUL DEBITOR PENTRU NOTA DE INCASARE 5311 = 4111` | idem | idem |
|
||||
|
||||
(`VARDESC` mentioneaza si `= 4111` — nu e o eroare de copy-paste: e nota ca partea de credit a
|
||||
notei de incasare e contul de clienti `4111`, deci descrierea documenteaza ambele parti ale notei,
|
||||
nu doar valoarea implicita a optiunii.)
|
||||
|
||||
**Insertia originala** (`SCRIPTURI_CLAR/2010/08/ff_2010_08_11_01_OPTIUNI.sql:9-19`), tiparul de
|
||||
migrare de urmat:
|
||||
|
||||
```sql
|
||||
insert into optiuni (VARNAME, VARTYPE, PROGRAM, VARVALUE, VARDESC, ID_PRG_OWNER, PROGRAME)
|
||||
values ('RF_CONT_INCASARE_BONFISCAL', 'CHARACTER', 'ROAFACTURARE', '5311',
|
||||
'CONTUL DEBITOR PENTRU NOTA DE INCASARE 5311 = 4111', null, 'ROAFACTURARE');
|
||||
```
|
||||
|
||||
**Apelul din PL/SQL** (`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:13161-13234`,
|
||||
`PROCEDURE scrie_incasare2`), citit integral — **exact tiparul "optiune cu fallback hardcodat" cerut
|
||||
de decizia 36**:
|
||||
|
||||
```sql
|
||||
V_SCD := V_PSCD; -- parametru explicit, daca a fost dat de apelant (ff_...:13185)
|
||||
CASE
|
||||
WHEN V_TIP = nTipIncasareBonFiscal THEN
|
||||
...
|
||||
IF V_SCD IS NULL THEN
|
||||
V_SCD := PACK_SESIUNE.getoptiunefirma('RF_CONT_INCASARE_BONFISCAL');
|
||||
END IF;
|
||||
IF V_SCD IS NULL THEN
|
||||
V_SCD := '5311'; -- fallback hardcodat, instalare veche
|
||||
END IF;
|
||||
...
|
||||
END CASE;
|
||||
```
|
||||
|
||||
Trei niveluri, in ordine: **(1) parametru explicit daca a fost dat, (2) optiunea de firma, (3)
|
||||
constanta hardcodata** — folosita si daca randul din `OPTIUNI` lipseste complet (instalare veche,
|
||||
migrare neaplicata) *si* daca a fost sters/golit manual din ecran. Acest cont ajunge apoi direct in
|
||||
`INSERT INTO ACT_TEMP (..., SCD, ...)` (`ff_...:13241-13249` in continuare), **fara nicio validare
|
||||
suplimentara de format sau de existenta in planul de conturi** — nici in Oracle, nici in ecranul de
|
||||
optiuni (sectiunea 1.3), nici la `INSERT`.
|
||||
|
||||
**Ecranul de editare**: acelasi `frm_optiuni`/`frm_optiuni_nou` generic (sectiunea 1.3) — niciun
|
||||
ecran dedicat pentru aceste 4 optiuni, se editeaza ca text simplu in grid.
|
||||
|
||||
### 2b. `D406` — optiune numerica/booleana folosita ca flag, cu conversie explicita la citire
|
||||
|
||||
`VARNAME='D406'`, `VARTYPE='NUMERIC'`, `VARVALUE='1'`, `PROGRAM='ROACONT'`,
|
||||
`PROGRAME='ROACONT,ROAAUTO,ROACONTRACTE,ROADEVIZE,ROAFACTURARE,...'`. Citita in acelasi
|
||||
`scrie_incasare2` (`ff_...:13177`): `lnD406 := TO_NUMBER(NVL(pack_sesiune.getoptiunefirma('D406'),
|
||||
'0'))` — **tipar de citire pentru optiune numerica**: `getoptiunefirma` intoarce mereu text,
|
||||
apelantul face `NVL(...,'0')` inainte de `TO_NUMBER` (aici fallback-ul `'0'` e in acelasi loc cu
|
||||
apelul, nu separat ca la `RF_CONT_INCASARE_*` — variatie minora de stil intre autori, nu o regula
|
||||
diferita).
|
||||
|
||||
### 2c. `CONT411` si `DEVCONTALTELE` — optiuni "cont" mai vechi, azi orfane in cod
|
||||
|
||||
`CONT411` (`VARTYPE='CHARACTER'`, `VARVALUE='4111'`, `VARDESC='411 SAU 4111'`, fara `PROGRAM`) si
|
||||
`DEVCONTALTELE` (`VARTYPE='CHARACTER'`, `VARVALUE='704'`, `VARDESC='CONT ALTE SERVICII FACTURA'`,
|
||||
`PROGRAM='ROAAUTO'`) — cautare `grep` pentru ambele in tot `SCRIPTURI_CLAR`: **niciun apelant activ
|
||||
gasit** (doar insertiile originale din 2011). Probabil cod dezafectat sau folosit doar din alt
|
||||
produs/raport neexaminat aici. Mentionate pentru completitudine si pentru ca `CONT411` confirma
|
||||
**exact valoarea implicita ceruta de Marius** (`4111`) ca precedent de configurare tot pe un cont
|
||||
de clienti — dar **nu** au un lant activ de apel, deci nu sunt un exemplu de "tipar in uz" la fel de
|
||||
tare ca `RF_CONT_INCASARE_*`.
|
||||
|
||||
### 2d. `EFACTURA_CONT_ART_E`/`EFACTURA_CONT_ART_P` — optiune "cont implicit pe articol", azi goala
|
||||
|
||||
`VARTYPE='CHARACTER'`, `VARVALUE` **goala** azi (nesetata pe baza curenta), `VARDESC='CONT ARTICOLE
|
||||
IMPLICIT FACTURI EMISE'`/`'...PRIMITE'`, `PROGRAM='ROACONT'`. Confirma ca tiparul "cont implicit
|
||||
configurabil, gol pana e setat manual" exista deja ca optiune (nu s-a putut verifica in aceasta
|
||||
runda cine o citeste — cautare in afara scopului, modulul `ROACONT`/eFactura nu a fost deschis).
|
||||
Relevanta doar ca a patra confirmare a conventiei de nume (`EFACTURA_CONT_ART_<sufix>`), nu ca lant
|
||||
complet verificat.
|
||||
|
||||
---
|
||||
|
||||
## 3. Propunerea concreta
|
||||
|
||||
### 3.1 Cheia optiunii
|
||||
|
||||
**`FACT_SCD_ARTFPRET`** — respecta conventia dominanta observata in tabel: prefix de modul
|
||||
(`FACT` pentru optiunile specifice `pack_facturare`/ROAFACTURARE, ca `FACTSETURI`,
|
||||
`FACTALGREPCOM`, `FACTURACUCHITANTA`, `FACTURAEMAIL`, `FACTURASOLD` — toate `PROGRAM='ROAFACTURARE'`)
|
||||
plus sufix descriptiv scurt fara diacritice si fara spatii (`SCD` = campul contabil vizat,
|
||||
`ARTFPRET` = "articol fara politica de pret" — in oglinda cu numele deja folosit in proiectarea
|
||||
anterioara, "ramura fara politica"). Nicio cheie `SCD`/`SCC` existenta azi in tabel (cautare directa,
|
||||
zero rezultate) — nu exista risc de coliziune de nume. **Alternativa mai scurta**, daca Marius
|
||||
prefera: `SCD_FARA_POLITICA` (fara prefix de modul) — tiparul din tabel nu e strict, exista si chei
|
||||
fara prefix (`D406`, `CONT411`); prefixul `FACT_` e recomandat, nu obligatoriu.
|
||||
|
||||
### 3.2 Valoarea implicita si unde traieste
|
||||
|
||||
**Tiparul deja stabilit si folosit de `RF_CONT_INCASARE_*` (sectiunea 2a) se copiaza identic**:
|
||||
implicitul traieste **in doua locuri**, nu unul:
|
||||
|
||||
1. **In randul din `OPTIUNI`, la instalare** — valoarea `VARVALUE='4111'` scrisa direct de scriptul
|
||||
de migrare (sectiunea 3.4). Asta e valoarea pe care o vede si o poate schimba Marius/clientul din
|
||||
`frm_optiuni`, fara recompilare — exact cerinta deciziei 36.
|
||||
2. **Hardcodat in PL/SQL, langa apelul `getoptiunefirma`**, ca a doua plasa de siguranta pentru
|
||||
instalari vechi unde migrarea nu a rulat inca sau randul a fost sters/golit manual din ecran:
|
||||
|
||||
```sql
|
||||
V_SCD := PACK_SESIUNE.getoptiunefirma('FACT_SCD_ARTFPRET');
|
||||
IF V_SCD IS NULL THEN -- acopera si randul lipsa, si VARVALUE gol/sters din ecran (sectiunea 1.2)
|
||||
V_SCD := '4111';
|
||||
END IF;
|
||||
```
|
||||
|
||||
Plasata **in ramura noua din `contabilizeaza_articol`** (`canal_cont_venit_fara_politica.md:146`,
|
||||
randul `SCD` din tabelul design-ului), **in locul** liniei `SCD := '4111'` hardcodat propuse acolo —
|
||||
inlocuire punctuala, restul ramurii (`ASCD` calculat din `GetAnaliticByGrupUtilizatori(...,
|
||||
V_SCD)`, deja dependent de `V_SCD`) ramane neschimbat, primeste automat valoarea corecta indiferent
|
||||
de sursa (optiune sau fallback).
|
||||
|
||||
### 3.3 Comportamentul cand optiunea lipseste sau e invalida
|
||||
|
||||
- **Lipseste randul din `OPTIUNI`** (instalare veche, migrare neaplicata): `getoptiunefirma` intoarce
|
||||
`''` -> tratat `IS NULL` in Oracle -> fallback la `'4111'` hardcodat (sectiunea 3.2, pasul 2).
|
||||
**Acoperit prin constructie**, acelasi tipar ca `RF_CONT_INCASARE_*`.
|
||||
- **Randul exista dar `VARVALUE` e gol** (sters manual din ecran, fara sa fi fost stearsa cheia):
|
||||
identic cazului anterior — `''` tot `IS NULL` in Oracle. **Acoperit prin constructie.**
|
||||
- **`VARVALUE` contine un cont care nu exista in planul de conturi**, sau un string cu lungime/
|
||||
format invalid: **nu exista nicio validare azi**, nici in `getoptiunefirma`, nici in ecranul de
|
||||
optiuni (sectiunea 1.3, "nicio validare de format sau de continut"), nici la `INSERT INTO
|
||||
ACT_TEMP` (coloana `SCD` e `VARCHAR2(4) NULL` fara `CHECK`, fara `FK`, confirmat in DDL-ul deja
|
||||
citat in `canal_cont_venit_fara_politica.md`, sectiunea DDL). Un cont inexistent ar ajunge pe nota
|
||||
neschimbat — **exact acelasi risc pe care il are deja `RF_CONT_INCASARE_*` de 15 ani**, nu unul
|
||||
nou introdus de aceasta propunere. Daca se doreste o garda, ar trebui adaugata **la citire** (in
|
||||
ramura noua din `contabilizeaza_articol`, dupa cele doua atribuiri de mai sus, cu un `SELECT
|
||||
COUNT(*) FROM PLCONT WHERE COD_CONT = V_SCD` sau echivalent) — **nu exista azi un precedent de
|
||||
validare pe niciuna dintre optiunile de tip cont examinate**, deci adaugarea uneia ar fi o
|
||||
imbunatatire noua, nu o continuare a unui tipar existent; de decis explicit (sectiunea 4).
|
||||
- **Lungime > 4 caractere** in `VARVALUE` (campul din tabel e `VARCHAR2(1000)`, mult mai lat decat
|
||||
`ACT_TEMP.SCD VARCHAR2(4)`): ar da `ORA-12899` (value too large) la `INSERT INTO ACT_TEMP` daca
|
||||
cineva scrie din greseala un text lung in ecran — eroare Oracle standard, nu un `NULL`/gol tacut;
|
||||
comportament acceptabil ca gardă minimala "gratuita", fara cod suplimentar.
|
||||
|
||||
### 3.4 Scriptul de migrare — schita, neaplicata
|
||||
|
||||
Tiparul exact al insertiei `RF_CONT_INCASARE_BONFISCAL` (sectiunea 2a), idempotent prin verificarea
|
||||
existentei prealabile (tipar comun in scripturile de migrare vazute in `SCRIPTURI_CLAR`, desi
|
||||
insertia originala din 2010 nu avea garda — cea de mai jos o adauga, mai sigura pentru un script
|
||||
care ar putea rula de doua ori):
|
||||
|
||||
```sql
|
||||
-- ff_<data>_NN_COMUN_OPTIUNI.sql (schita, neaplicata)
|
||||
DECLARE
|
||||
V_CNT NUMBER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO V_CNT FROM OPTIUNI WHERE UPPER(TRIM(VARNAME)) = 'FACT_SCD_ARTFPRET';
|
||||
IF V_CNT = 0 THEN
|
||||
INSERT INTO OPTIUNI (VARNAME, VARTYPE, PROGRAM, VARVALUE, VARDESC, PROGRAME)
|
||||
VALUES ('FACT_SCD_ARTFPRET', 'CHARACTER', 'ROAFACTURARE', '4111',
|
||||
'CONTUL DEBITOR (SCD) PENTRU ARTICOL FACTURAT FARA POLITICA DE PRET',
|
||||
'ROAFACTURARE');
|
||||
END IF;
|
||||
END;
|
||||
/
|
||||
exec pack_migrare.UpdateVersiune('ff_<data>_NN_COMUN_OPTIUNI');
|
||||
commit;
|
||||
```
|
||||
|
||||
`ID_PRG_OWNER` omis (rand `NULL` in toate exemplele citate, coloana pare neutilizata activ pentru
|
||||
optiuni de firma simple). `PROGRAME='ROAFACTURARE'` — restrans, spre deosebire de
|
||||
`RF_CONT_INCASARE_*` care listeaza 6 produse; de largit doar daca se confirma (in afara scopului
|
||||
acestei cercetari, sectiunea "Ce ramane de decis") ca alte produse din suita ajung sa apeleze aceeasi
|
||||
ramura din `contabilizeaza_articol` prin `adauga_articol_factura` simplu.
|
||||
|
||||
### 3.5 Validare la salvare in ecran
|
||||
|
||||
**Nu** — confirmat la sectiunea 1.3: `frm_optiuni_nou` valideaza doar "camp nevid" pe cele 3 campuri
|
||||
principale, nicio validare specifica per-cheie. Optiunea noua se comporta identic cu restul tabelei:
|
||||
utilizatorul poate scrie orice text de pana la 1000 caractere in `VARVALUE`, cu riscurile descrise
|
||||
la sectiunea 3.3.
|
||||
|
||||
---
|
||||
|
||||
## 4. Ce ramane de decis
|
||||
|
||||
1. **Numele exact al cheii** — `FACT_SCD_ARTFPRET` propus (sectiunea 3.1); Marius poate prefera alt
|
||||
nume sau formatul mai scurt `SCD_FARA_POLITICA`.
|
||||
2. **Se adauga o validare a contului la citire** (cont trebuie sa existe in planul de conturi) sau
|
||||
se pastreaza tiparul actual, fara validare, ca la `RF_CONT_INCASARE_*`? Niciun exemplu existent nu
|
||||
valideaza — a introduce validare ar fi prima data cand se face asta pe o optiune de tip cont.
|
||||
3. **`PROGRAME` restrans la `ROAFACTURARE` sau extins** ca la `RF_CONT_INCASARE_*` (6 produse)? Tine
|
||||
de raspunsul, inca deschis in `parametru_cont_contabilizeaza_articol.md` sectiunea 2d, la
|
||||
intrebarea daca alte produse din suita apeleaza `adauga_articol_factura` simplu.
|
||||
4. **Textul exact din `VARDESC`** — schita de mai sus e o propunere, nu o cerinta.
|
||||
|
||||
---
|
||||
|
||||
## Ce nu s-a putut stabili
|
||||
|
||||
- **Cine citeste azi `EFACTURA_CONT_ART_E`/`EFACTURA_CONT_ART_P`** (sectiunea 2d) — cautarea s-a
|
||||
oprit la insertia originala; modulul `ROACONT`/eFactura nu a fost deschis, in afara scopului
|
||||
cerut (exemplul a servit doar ca a patra confirmare de conventie de nume, nu ca lant complet).
|
||||
- **Definitia view-ului VFP local `v_optiuni`** (folosit ca `RecordSource` in `frm_optiuni`) — e un
|
||||
cursor/view generat dinamic prin `gencursor()`/`goExecutor.oExecute()` din `oinit_optiuni.prg`, nu
|
||||
un obiect Oracle persistent (interogare `DBMS_METADATA` pe `V_OPTIUNI` a esuat cu "object not
|
||||
found", asteptat) — nu schimba nimic din propunere, doar noteaza ca numele nu desemneaza o vedere
|
||||
Oracle.
|
||||
- **Daca `CONT411`/`DEVCONTALTELE` (sectiunea 2c) mai au vreun apelant in alt produs din suita**
|
||||
(ROACONT, ROAGEST etc.) — cautarea s-a limitat la `SCRIPTURI_CLAR` (tot codul PL/SQL istoric); nu
|
||||
s-a cautat in `.vc2`/`.prg` din alte COMUN-uri de produs.
|
||||
210
docs/cercetare/pack_auto_actualizeaza_deviz.md
Normal file
210
docs/cercetare/pack_auto_actualizeaza_deviz.md
Normal file
@@ -0,0 +1,210 @@
|
||||
# Cercetare: `pack_auto.actualizeaza_deviz` si riscul asupra devizului ROAAUTO la #13
|
||||
|
||||
Scop: `plan_13_unificare_formular_facturare.md` vrea sa permita adaugarea unei linii noi pe o
|
||||
factura deja emisa, inclusiv pe facturile ROAAUTO (`tip = -12`). Intrebarea: ce se intampla cu
|
||||
**devizul** din ROAAUTO cand factura primeste o linie pe care devizul nu o stie. Punctul de
|
||||
plecare a fost `pack_auto.actualizeaza_deviz`, apelat din
|
||||
`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1310`, al carei corp nu fusese citit inainte de
|
||||
aceasta cercetare.
|
||||
|
||||
Continua `roaauto_facturi.md` si `roaauto_articole_lista_preturi.md` (deja citite, nu reiau
|
||||
constatarile lor: acelasi `PACK_FACTURARE`, `VANZARI_DETALII_TEMP` -> `VANZARI_DETALII`,
|
||||
`tip=-12`, liniile sintetice `-100000..-100008` plus liniile reale "Alte servicii", editorul
|
||||
`frm_modific2024` read-only, `oviz_devize.vc2:4536` blocheaza modificarea dupa `nrfact`).
|
||||
|
||||
## 1. Unde e definit `PACK_AUTO`
|
||||
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\<an>\<luna>\ff_..._AUTO_PACK_AUTO.sql` — 14 versiuni istorice
|
||||
gasite (2013-2026). Sursa de adevar folosita (cea mai recenta, dupa conventia deja aplicata in
|
||||
`roaauto_facturi.md` pentru `PACK_FACTURARE`):
|
||||
|
||||
**`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql`** (1804 linii,
|
||||
antet "17.03.2026 / robert / creare procedura setOptiuneInchidere").
|
||||
|
||||
Semnatura in spec: `:74-76`. Corp: `:692-733`.
|
||||
|
||||
## 2. Ce face `actualizeaza_deviz`, pas cu pas
|
||||
|
||||
Corpul complet (`ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`):
|
||||
|
||||
```sql
|
||||
procedure actualizeaza_deviz(tnProcTvav IN NUMBER,
|
||||
tcSirIdOrdl IN VARCHAR2,
|
||||
tnIdSet IN NUMBER) is
|
||||
lcSeparator VARCHAR2(1) := ',';
|
||||
lnIdFact DOCUMENTE.ID_DOC%TYPE;
|
||||
begin
|
||||
-- nu iau pack_contafin.get_idfact pentru ca s-ar putea sa am id_fact de la incasare, nu de la factura
|
||||
SELECT MAX(ID_FACT)
|
||||
INTO lnIdFact
|
||||
FROM ACT
|
||||
WHERE COD = pack_contafin.get_cod()
|
||||
AND SCD NOT LIKE '5%';
|
||||
|
||||
UPDATE DEV_ORDL
|
||||
SET PROC_TVAV = tnProcTvav
|
||||
WHERE ID_ORDL IN (SELECT X FROM table(charn2collection(tcSirIdOrdl, lcSeparator)));
|
||||
|
||||
UPDATE /*+ INDEX(RUL IDX_RUL_001) */ RUL
|
||||
SET ID_FACT = lnIdFact
|
||||
WHERE ID_SET <> 229
|
||||
AND ID_LUCRARE IN (SELECT ID_LUCRARE FROM DEV_ORDL
|
||||
WHERE ID_ORDL IN (SELECT X FROM table(charn2collection(tcSirIdOrdl, lcSeparator))));
|
||||
|
||||
IF tnIdSet IN (31003, 31004, 31005, 31006, 31007, 31011) THEN
|
||||
UPDATE NOM_LUCRARI
|
||||
SET ID_FACT = lnIdFact
|
||||
WHERE ID_LUCRARE IN (SELECT ID_LUCRARE FROM DEV_ORDL
|
||||
WHERE ID_ORDL IN (SELECT X FROM table(charn2collection(tcSirIdOrdl, lcSeparator))));
|
||||
END IF;
|
||||
end actualizeaza_deviz;
|
||||
```
|
||||
|
||||
Trei pasi, toti indexati pe `ID_ORDL`/`ID_LUCRARE` (comanda/lucrarea din ROAAUTO), **niciunul pe
|
||||
`VANZARI`/`VANZARI_DETALII`**:
|
||||
|
||||
1. Citeste `lnIdFact` = cel mai mare `ID_FACT` din `ACT` pentru "codul" curent de sesiune
|
||||
(`pack_contafin.get_cod()`), excluzand notele de incasare (`SCD NOT LIKE '5%'`) — comentariul
|
||||
din cod explica de ce: la momentul apelului pot exista in `ACT` atat o nota de incasare cat si
|
||||
nota/factura propriu-zisa, si vrea explicit factura.
|
||||
2. `UPDATE DEV_ORDL SET PROC_TVAV = tnProcTvav` — scrie cota de TVA pe comenzile din
|
||||
`tcSirIdOrdl` (lista CSV de `ID_ORDL`, despartita cu `charn2collection`).
|
||||
3. `UPDATE RUL SET ID_FACT = lnIdFact WHERE ID_SET <> 229 AND ID_LUCRARE IN (...)` — leaga
|
||||
inapoi randurile din `RUL` (jurnalul de manopera/materiale al lucrarii, folosit si la
|
||||
`frm_inchidere_productie.do_executa` pentru totalul pe sectii, vezi §4) de documentul creat.
|
||||
4. Doar daca `tnIdSet` e unul din `{31003,31004,31005,31006,31007,31011}`, acelasi `ID_FACT` se
|
||||
scrie si pe `NOM_LUCRARI` (nomenclatorul de lucrari).
|
||||
|
||||
**Nu exista niciun `SELECT`/`UPDATE` pe `VANZARI` sau `VANZARI_DETALII` in `actualizeaza_deviz`**
|
||||
si, verificat suplimentar, **in tot pachetul `PACK_AUTO`** (grep pe `VANZARI` in cele 1804 linii
|
||||
ale fisierului: 0 potriviri). Deci raspunsul direct la intrebarea din brief: procedura **nu
|
||||
recalculeaza niciun total al devizului din liniile facturii** — nu citeste liniile facturii deloc.
|
||||
Ce face e sa "stampileze" `ID_FACT` (documentul de vanzare/nota abia creat) pe randurile din `RUL`
|
||||
si `NOM_LUCRARI` legate de comenzile din `tcSirIdOrdl`, plus sa actualizeze cota TVA pe
|
||||
`DEV_ORDL`. E o legatura *deviz -> document*, nu un recalcul *document -> total deviz*.
|
||||
|
||||
## 3. Ce se intampla cu o linie de factura pe care devizul nu o are
|
||||
|
||||
Raspunsul e neted, pentru ca premisa intrebarii (un `SUM` peste `VANZARI_DETALII`) nu exista in
|
||||
cod: **procedura nu vede deloc liniile facturii**, nici cele cunoscute (sintetice
|
||||
`-100000..-100008`), nici cele reale (nomenclator, cazul "Alte servicii" din raportul anterior),
|
||||
nici una noua adaugata din afara. Ea opereaza exclusiv pe `ID_ORDL`/`ID_LUCRARE` (identitatea
|
||||
comenzii ROAAUTO), primite ca parametru `tcSirIdOrdl` — nu deriva niciodata acest identificator
|
||||
din continutul `VANZARI_DETALII`.
|
||||
|
||||
Precedentul "Alte servicii" (articole reale, `id_articol` pozitiv, cf.
|
||||
`roaauto_articole_lista_preturi.md` §2) confirma exact acest comportament pe date deja live: acele
|
||||
linii coexista cu liniile sintetice in `VANZARI_DETALII` de multa vreme, fara ca
|
||||
`actualizeaza_deviz` sa faca vreo distinctie — pentru ca nu se uita la `VANZARI_DETALII` deloc.
|
||||
|
||||
**Consecinta pentru #13**: adaugarea unei linii noi pe `VANZARI_DETALII` pentru o vanzare
|
||||
`tip=-12` nu poate "dezechilibra" ce scrie `actualizeaza_deviz`, pentru simplul motiv ca aceasta
|
||||
procedura nu depinde de continutul `VANZARI_DETALII`. Nu exista *eroare*, nu exista *ignorare
|
||||
explicita* — e o absenta totala de citire.
|
||||
|
||||
Important insa (nuanta pe care intrebarea din brief o presupune implicit gresit): asta **nu**
|
||||
inseamna ca "devizul" (asa cum e prezentat operatorului in ROAAUTO) ar reflecta automat noua
|
||||
linie. Totalul devizului afisat in `oviz_devize.vc2` e calculat client-side din `RUL` (vezi
|
||||
`crsTotaluri`, construit cu un `SELECT SUM(...) FROM RUL ... GROUP BY ID_SECTIE` in
|
||||
`oviz_devize.vc2:7816-7823`, in `frm_inchidere_productie.do_executa`) si din cursoarele
|
||||
`crsdeviz`/`crsalteserv` calculate in `oproceduri_devize.prg` la momentul facturarii — niciodata
|
||||
din `VANZARI_DETALII`. Deci o linie adaugata ulterior pe factura **nu va aparea niciodata** in
|
||||
totalul devizului asa cum il calculeaza ROAAUTO, indiferent daca `actualizeaza_deviz` mai ruleaza
|
||||
sau nu — pur si simplu acel total nu are o sursa care sa citeasca `VANZARI_DETALII`.
|
||||
|
||||
## 4. Cand e apelata
|
||||
|
||||
Cautare in ROAAUTO (`Grep` pe `actualizeaza_deviz`, cele trei aparitii de cod, plus istoric
|
||||
`.??2`) si in tot `D:\ROA\DATABASE\SCRIPTURI_CLAR` (niciun alt pachet Oracle nu o apeleaza — grep
|
||||
pe `actualizeaza_deviz` in tot arborele SCRIPTURI_CLAR gaseste doar fisierele care *definesc*
|
||||
`PACK_AUTO`/vechiul `PACK_DEVIZE`, nu vreun apel extern):
|
||||
|
||||
Trei puncte de apel, toate in VFP, niciunul intr-un trigger/job Oracle:
|
||||
|
||||
1. **La emiterea facturii** — `oproceduri_devize.prg:1310`, in acelasi bloc `lcSql` cu
|
||||
`pack_facturare.scrie_in_vanzari` (linia 1302), cu `tnIdSet` dinamic (`lnIdSet`, calculat mai
|
||||
sus in `factureaza_deviz`). Comentariu la linia 1311: *"am scos apelul catre
|
||||
`pack_devize.dev_completeaza_rul`; e inclus in `actualizeaza_deviz`"* — confirma ca functia a
|
||||
inlocuit o procedura mai veche cu acelasi scop (legare RUL -> document).
|
||||
2. **`frm_inchidere_productie.do_executa`** (`oviz_devize.vc2:7896`, clasa la `:7143`) — cu
|
||||
`tnIdSet` **fix, 31007** — apelata dupa ce formularul de **inchidere productie** scrie o
|
||||
*nota contabila* (`oscrie_in_fisiere()` peste cursorul `actactan`, `:7889`), **nu** o factura;
|
||||
nu apeleaza `pack_facturare.scrie_in_vanzari`, deci nu atinge `VANZARI` deloc pe acest drum.
|
||||
3. **`frm_inchidere_regie.do_executa`** (`oviz_devize.vc2:8907`, clasa la `:8093`) — identic,
|
||||
`tnIdSet` fix **31006**, pentru inchiderea de **regie** (overhead), tot printr-o nota
|
||||
contabila, nu o factura.
|
||||
|
||||
Deci `actualizeaza_deviz` nu e specifica facturarii — e o operatie generica *"leaga
|
||||
comenzile/lucrarile astea de ultimul document contabil creat pentru codul curent"*, refolosita si
|
||||
la doua fluxuri de inchidere interna care nu produc facturi de vanzare. In niciunul din cele trei
|
||||
cazuri nu e re-declansata mai tarziu (nu exista un al patrulea apel, nici din UI, nici din
|
||||
schedule/trigger Oracle) — deci editarea ulterioara a unei facturi deja emise (obiectivul #13) nu
|
||||
ar re-invoca automat aceasta procedura, pentru ca nimic din codul citit o cheama in afara celor 3
|
||||
puncte de mai sus.
|
||||
|
||||
## 5. Alte proceduri din `PACK_AUTO` care citesc `VANZARI`/`VANZARI_DETALII` pentru un deviz
|
||||
|
||||
**Niciuna.** Grep pe `VANZARI` in intregul fisier `ff_2026_03_31_02_AUTO_PACK_AUTO.sql` (1804 de
|
||||
linii, tot pachetul, nu doar `actualizeaza_deviz`) — 0 potriviri. `PACK_AUTO` nu are nicio
|
||||
dependenta de tabelele de vanzari/facturare; toata legatura cu partea financiara trece prin `ACT`
|
||||
(note contabile) si `DEV_ORDL`/`RUL`/`NOM_LUCRARI` (structura proprie ROAAUTO).
|
||||
|
||||
Singurul loc care **citeste** liniile facturii pentru re-listarea unui deviz e in afara
|
||||
`PACK_AUTO`, pe partea VFP: `relisteaza_factura_deviz`
|
||||
(`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1516-1576`, mentionata in raportul anterior).
|
||||
Verificat acum sursa: interogheaza direct view-urile COMUN `fact_vfacturi`
|
||||
(`:1531`, filtrat `WHERE cod = ...`) si **`fact_vfacturi_detalii`** (`:1564`,
|
||||
`select * from fact_vfacturi_detalii where id_vanzare = <poDate.nid_vanzare>` — fara alt filtru).
|
||||
View-ul e definit in
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2024\06\ff_2024_06_04_01_COMUN_FACTURARE.sql:8-94`:
|
||||
`create or replace view fact_vfacturi_detalii as select ... from vanzari_detalii a left join
|
||||
nom_articole b ... ` — un simplu wrapper peste `VANZARI_DETALII` cu join-uri de denumire, **fara
|
||||
nicio clauza `WHERE`** care sa restranga la id-uri de articol cunoscute sau la liniile
|
||||
"originale" ale devizului.
|
||||
|
||||
**Consecinta**: o linie noua inserata in `VANZARI_DETALII` pentru acel `id_vanzare` **va aparea
|
||||
automat** la o re-listare/re-tiparire ulterioara prin `relisteaza_factura_deviz` — comportament
|
||||
asteptat de la un view neconditionat, nu o stricare. Nu exista risc de eroare sau de excludere pe
|
||||
acest drum; riscul (daca exista) e doar de **asteptare a utilizatorului**: factura retiparita va
|
||||
arata linia noua, dar ecranul de deviz din ROAAUTO (total calculat din `RUL`, §3) nu o va arata
|
||||
niciodata, pentru ca nu deriva din `VANZARI_DETALII`.
|
||||
|
||||
## 6. Concluzie operationala pentru #13
|
||||
|
||||
**Da, se poate adauga o linie pe o vanzare `tip=-12` fara sa "strice" `pack_auto.actualizeaza_deviz`
|
||||
sau vreo alta procedura din `PACK_AUTO`** — pentru ca niciuna din ele nu citeste `VANZARI`/
|
||||
`VANZARI_DETALII`; nu exista mecanism Oracle-side care sa recalculeze/verifice totalul devizului
|
||||
din liniile facturii, deci nu exista nimic de dezechilibrat la acel nivel. Nu e nevoie de niciun
|
||||
apel Oracle suplimentar din partea ROAFACTURARE ca sa "anunte" ROAAUTO despre linia noua —
|
||||
`actualizeaza_deviz` n-ar face nimic util cu acea informatie oricum (opereaza pe `ID_ORDL`, nu pe
|
||||
linii de factura).
|
||||
|
||||
Ce **nu** rezolva aceasta concluzie, si ramane responsabilitatea planului #13, nu a acestei
|
||||
proceduri:
|
||||
|
||||
- **Desincronizare de afisare, nu de date**: totalul devizului aratat in ROAAUTO
|
||||
(`oviz_devize.vc2`, calculat din `RUL`/cursoarele de facturare) nu va reflecta niciodata o
|
||||
linie adaugata ulterior direct pe `VANZARI_DETALII` — nici azi (cu liniile "Alte servicii"),
|
||||
nici cu o linie noua din editorul unificat. Daca produsul vrea ca devizul sa "stie" de linia
|
||||
noua vizual, ar trebui cod nou in ROAAUTO (nu exista azi), nu un apel catre `actualizeaza_deviz`.
|
||||
- **Reprintarea** (`relisteaza_factura_deviz`) va include automat linia noua (§5) — de verificat
|
||||
daca asta e comportamentul dorit de produs sau o suprindere pentru operatorul ROAAUTO.
|
||||
- Confirmat si direct in cod ROAFACTURARE: `Grep` pe `pack_auto` in
|
||||
`D:\ROA\ROAFACTURARE` (inclusiv `COMUN`) nu gaseste nicio referinta in cod, doar in `docs\`
|
||||
(planul #13 si aceste rapoarte) — azi ROAFACTURARE nu apeleaza deloc `PACK_AUTO`, confirmand ca
|
||||
nu exista deja o presupunere ascunsa de sincronizare intre cele doua.
|
||||
|
||||
## Neverificat
|
||||
|
||||
- Ce reprezinta exact tabelele `ACT`/`RUL`/`NOM_LUCRARI`/`DEV_ORDL` la nivel de schema completa
|
||||
(DDL/comentarii) — inteles doar din felul in care sunt folosite in cod, nu dintr-un dictionar de
|
||||
date citit explicit.
|
||||
- Daca vreun raport fiscal/SAF-T (mentionat generic in `ff_2022_03_24_01_COMUN_SAFT.sql`, nedeschis)
|
||||
agrega `VANZARI_DETALII` intr-un mod care ar fi afectat de o linie noua pe `tip=-12` — in afara
|
||||
ariei `PACK_AUTO` cerute, nu a fost investigat.
|
||||
- Daca `frm_incasare_finala.do_recalculeaza_total`/`calculeaza_total` (`oviz_devize.vc2:6494,6700`)
|
||||
ar putea fi reconciliate manual de un operator ca sa "vada" o linie adaugata ulterior — nu am
|
||||
citit corpul acestor metode, doar semnalul ca exista.
|
||||
- Istoricul complet al celorlalte 13 versiuni `AUTO_PACK_AUTO.sql` nu a fost comparat linie cu
|
||||
linie cu cea din 2026-03 — presupun (conform conventiei deja aplicate pentru `PACK_FACTURARE`)
|
||||
ca cea mai recenta e sursa de adevar pentru comportamentul curent din productie.
|
||||
173
docs/cercetare/parametru_cont_contabilizeaza_articol.md
Normal file
173
docs/cercetare/parametru_cont_contabilizeaza_articol.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# Verificare adversariala — proiectarea parametrului de cont (`canal_cont_venit_fara_politica.md`, sectiunea "Proiectarea parametrului de cont contabil")
|
||||
|
||||
Mandat: **nu re-proiecta** — proiectarea exista deja in `canal_cont_venit_fara_politica.md:13-227`
|
||||
(citita integral). Aici: incercare de a o sparge + inchiderea celor 4 goluri semnalate de autor.
|
||||
Sursa PL/SQL: aceeasi, `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(17217 linii). Oracle: doar `SELECT`, schema `MARIUSM_AUTO`/`ROA_CENTRAL`. VFP: doar `vfp_symbols.ps1`
|
||||
(index, read-only). **Niciun fisier de cod atins.**
|
||||
|
||||
## Verdict, in patru randuri
|
||||
|
||||
**Proiectarea rezista la verificarea adversariala** — nu am gasit nicio eroare care sa-i invalideze
|
||||
concluzia centrala. Am gasit **un rand din tabel mai slab decat descris** (`CU_TVA=1` hardcodat NU e
|
||||
complet inofensiv — are un efect colateral masurabil, minor, prin `nproc_tva_max`), **un rand mai
|
||||
tare decat descris** (`IN_VALUTA` din `nin_valuta` e o sursa solida, nu doar plauzibila — parametru
|
||||
obligatoriu, fara `DEFAULT`), **o excludere confirmata corecta prin structura schemei** (articolul
|
||||
"compus" nu poate exista pe o linie fara politica — nu o gaura), si **un gol raportat de autor acum
|
||||
inchis cu fapt** (cele doua clase de la `:14069`/`:18089` sunt distincte, nu o duplicare a aceleiasi
|
||||
metode — 2 locuri VFP de atins, nu 1). Pe decizia 35 (regenerare = acelasi cod): **DA, calea Oracle
|
||||
e identica**, dar exista un obstacol real, deja proiectat separat (`idfact_refolosire_si_documente.md`),
|
||||
**in alt pachet** (`PACK_CONTAFIN`), nu in `pack_facturare` — nu invalideaza design-ul curent, dar
|
||||
regenerarea nu e completa fara acea a doua bucata de lucru.
|
||||
|
||||
---
|
||||
|
||||
## 1. Decizia 35 — regenerarea foloseste identic `pack_facturare`?
|
||||
|
||||
**DA pe calea Oracle relevanta pentru parametrul de cont, cu o exceptie deja cunoscuta si separata.**
|
||||
|
||||
Verificat direct: `scrie_factura2` reseteaza starea de sesiune la fiecare apel prin
|
||||
`initializeaza_date_factura` (`PACK_FACTURARE:1808-1846`), care face
|
||||
`DELETE FROM VANZARI_DETALII_TEMP;` (`:1835`) si `pack_facturare.nid_act := 0;` (`:1836`) —
|
||||
**contorul folosit de `scrie_nota`/`scrie_discount`/`scrie_tva` la fiecare `INSERT INTO ACT_TEMP`
|
||||
porneste curat la fiecare emitere, inclusiv la o a doua** (regenerare). Nimic in
|
||||
`contabilizeaza_articol`, ramura noua inclusa, citeste vreo stare care ar presupune "documentul e
|
||||
nou" — toate variabilele folosite (`nid_venchelt`, `nid_sectie_stoc`, `nin_valuta`, `nid_set`,
|
||||
`nid_util`) sunt **citite**, nu verificate contra unei stari anterioare.
|
||||
|
||||
**Obstacolul real e in `PACK_CONTAFIN`, nu in `pack_facturare`**: reemiterea cu acelasi `ID_FACT` (ca
|
||||
sa nu se dubleze documentul contabil la regenerare) ar da azi `ORA-00001` pe `PK_DOCUMENTE`, pentru ca
|
||||
`SET_IDFACT` ia mereu `SEQ_IdFact.NEXTVAL` si documentul vechi ramane in tabel doar soft-sters
|
||||
(`STERS=1`), nu disparut — analiza completa, cu solutia (variabila de pachet noua in `PACK_CONTAFIN`,
|
||||
`MERGE ... WHEN MATCHED` pe `DOCUMENTE`), e deja facuta integral in
|
||||
`idfact_refolosire_si_documente.md` (sectiunile A-C). **E exact "ceva care ar cere cod separat" — dar
|
||||
separat inseamna alt pachet/alta procedura (`SET_IDFACT`, scrierea in `DOCUMENTE`), nu o ramura in
|
||||
plus in `contabilizeaza_articol` sau `adauga_articol_factura`.** Ramane o bucata de lucru distincta,
|
||||
necesara pentru ca regenerarea sa fie completa, dar nu intersecteaza si nu invalideaza design-ul
|
||||
parametrului de cont.
|
||||
|
||||
**Un gol neadresat de design, gasit acum**: ramura `pack_facturare.ntip = 4` ("facturare din avize",
|
||||
`:7520-7537`) apeleaza `scrie_fact_aviz_custodie` in loc de `descarca_gestiune`+discount — design-ul
|
||||
nu spune ce se intampla pe aceasta ramura in cazul fallback. In practica, **probabil nu conteaza**:
|
||||
"facturare din aviz" cere azi ca linia sursa sa aiba deja `id_pol` (`adauga_articol_factura:5096`,
|
||||
`AND A.ID_POL = V_ID_POL` pe `VANZARI_DETALII` a avizului sursa) — un aviz fara politica n-ar fi
|
||||
ajuns niciodata pana la factura pe acest drum, deci scenariul "fallback pe `ntip=4`" e putin probabil
|
||||
sa apara. **Dar design-ul nu spune asta explicit** — de adaugat o linie care sa acopere/exclude
|
||||
explicit acest caz inainte de implementare, nu de presupus tacit.
|
||||
|
||||
## 2. Cele 4 goluri semnalate de autor
|
||||
|
||||
### 2a. `ofacturare.vc2:14069`/`:18089` — doua metode sau o duplicare?
|
||||
|
||||
`vfp_symbols.ps1 -Where 'ofacturare.vc2:14069'` -> **`frm_facturare_articole.do_scrie_articole`**
|
||||
(`ofacturare.vc2:13967-14195`). `-Where 'ofacturare.vc2:18089'` -> **`frm_facturare_articole2.do_scrie_articole`**
|
||||
(`ofacturare.vc2:18003-18221`). **Confirmat: doua clase distincte, `frm_facturare_articole` si
|
||||
`frm_facturare_articole2`, fiecare cu propria metoda `do_scrie_articole`** (acelasi nume de metoda,
|
||||
nu aceeasi clasa) — nu o duplicare literala a unui singur cod. **Consecinta pentru implementare**:
|
||||
parametrul nou trebuie cablat **in doua locuri VFP**, nu unul — ambele clase construiesc separat
|
||||
apelul RPC catre `adauga_articol_factura`. Nu schimba verdictul de fezabilitate, dar schimba
|
||||
suprafata de lucru VFP fata de impresia "un singur loc de atins".
|
||||
|
||||
### 2b. `scrie_tva` la cota 0% cu `CU_TVA` hardcodat `1` — efect real, nu inofensiv
|
||||
|
||||
Verificat corpul `scrie_nota` (`:12537-12558`): actualizarea `nproc_tva_max`/`nid_jtva_coloana`/
|
||||
`nTaxCode` **nu e neconditionata** — e in interiorul `IF V_CU_TVA = 1 THEN`, deci exact conditionata
|
||||
de flagul pe care design-ul propune sa-l hardcodeze. In interior insa, comparatia
|
||||
`IF pack_facturare.nproc_tva_max < V_PTVA THEN` **nu se uita la suma**, doar la rata — deci daca
|
||||
articolul fallback are `proc_tvav` real 0% (scutit) si `CU_TVA` e fortat la `1`, rata 0% **intra in
|
||||
comparatia de maxim** si, daca e prima/singura linie a documentului (`nproc_tva_max` initial `-1`,
|
||||
`:1885`), **castiga** — seteaza `nid_jtva_coloana`/`nTaxCode` la valorile acelei linii scutite.
|
||||
Aceste doua variabile sunt consumate mai departe **in aceeasi procedura `scrie_factura2`**, la
|
||||
discountul global pe factura (`:6164-6184`, `V_DISCOUNT_FACTURA <> 0`): rata/coloana/taxcode ale
|
||||
liniei scutite ar ajunge sa descrie linia de discount a **intregii facturi**, chiar daca alte linii
|
||||
au TVA real. **Verdict: `CU_TVA=1` hardcodat nu e "inofensiv" in toate cazurile cum spune design-ul —
|
||||
are un efect colateral real, dar restrans la o combinatie specifica (linie fallback cu TVA 0% +
|
||||
discount global pe factura + acea linie e cea cu rata "maxima" vazuta pana atunci).** Nu invalideaza
|
||||
alegerea `CU_TVA=1` ca implicit (majoritatea liniilor reale au TVA nenul), dar intareste, nu slabeste,
|
||||
cererea deja facuta de autor ("de confirmat cu Marius") — motivul de confirmat e mai concret decat
|
||||
"linie de TVA cu suma 0, inofensiv".
|
||||
|
||||
### 2c. `INSERT INTO VANZARI_DETALII ... FROM VANZARI_DETALII_TEMP` — linie exacta, confirmata
|
||||
|
||||
`PACK_FACTURARE:13705-13757`, in `scrie_in_vanzari` (spec `:13488`, apelata din `finalizeaza_factura`
|
||||
`:14787`, care e apelata la finalul fluxului de verificare — nu din `scrie_factura2`, alta faza a
|
||||
aceluiasi pipeline). **Confirmat: lista de coloane e explicita**, 24 coloane
|
||||
(`ID_VANZARE, ID_ARTICOL, LOT, SERIE, ID_RATA, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
|
||||
PRET, PROC_TVAV, ID_JTVA_COLOANA, ID_JTVA_COLOANA_EX, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT,
|
||||
EXPLICATIE, PRET_CU_TVA, DIFERENTA, CUSTODIE, ID_VANZARE_SET, ID_CTR, TAXCODE`) — **nu** `SELECT *`.
|
||||
Exact ce presupunea design-ul (sectiunea 2, citand `nota_contabila_fara_politica.md` fara
|
||||
re-verificare): daca se doreste ca `CONT_VENIT` sa ajunga si in `VANZARI_DETALII` (trasabilitate),
|
||||
**aceasta lista trebuie extinsa explicit** — fara acest pas, coloana noua ramane doar pe
|
||||
`VANZARI_DETALII_TEMP` si `ACT_TEMP`, invizibila in `VANZARI_DETALII` dupa fapt. Confirmarea nu
|
||||
schimba verdictul design-ului (deja marcase pasul ca "recomandat, nu strict necesar"), doar il
|
||||
transforma din presupunere in fapt verificat.
|
||||
|
||||
### 2d. Apelanti `adauga_articol_factura` din restul suitei
|
||||
|
||||
Sarit, cum a cerut team-lead-ul — alt agent lucreaza pe suprafata de regresie.
|
||||
|
||||
---
|
||||
|
||||
## 3. Verificare adversariala pe tabelul din design (sectiunea 3 a raportului sursa)
|
||||
|
||||
| Rand din tabel | Incercare de infirmare | Rezultat |
|
||||
|---|---|---|
|
||||
| `IN_VALUTA` <- `pack_facturare.nin_valuta` | E setat pe toate fluxurile relevante, sau ramane nul si azi nu conteaza? | **Mai solid decat descris.** `nin_valuta := V_IN_VALUTA` (`:1902`) e in `initializeaza_date_factura`, apelata **o data la inceputul fiecarei emiteri**, cu `V_IN_VALUTA IN NUMBER` **fara `DEFAULT`** in semnatura (`:1827`) — VFP e obligat sa trimita o valoare, nu poate omite parametrul. Nu e un fallback de sesiune care "poate ramane nesetat" (ca `nid_venchelt`), e un flag de document obligatoriu, mereu curent. |
|
||||
| `ID_VENCHELT`/`ID_SECTIE` <- variabile de sesiune | Cine le seteaza si cand? | **Confirmat, cu nuanta.** `nid_venchelt := V_ID_VENCHELT` si `nid_sectie_stoc := V_ID_SECTIE` (`:1876-1877`), tot in `initializeaza_date_factura`, din parametri **fara `DEFAULT`** insa **VFP poate trimite `NULL`** pe ei (sunt opționale ca *valoare*, nu ca prezenta in apel) — deci pot ramane `NULL` pe tot documentul. **Nu e o regresie**: pe ramura veche, `NVL(nid_venchelt, NVL(A.ID_VENCHELT, C.ID_VENCHELT))` ar da tot `NULL` daca nici politica nu are aceste campuri — acelasi rezultat posibil, aceeasi cauza (sesiune nesetata), nu unul nou introdus de fallback. |
|
||||
| Garda `descarca_gestiune` copiata identic | Acopera `id_gestiune=-1000` si `in_stoc`? | **Da, fara diferenta.** Garda foloseste `detalii_articol.id_gestiune`/`.in_stoc` — campuri populate identic de `adauga_articol_factura` indiferent de ramura (nu depind de politica azi, nici in design). Copierea literala a conditiei (`:7473-7475`) e corecta prin constructie. |
|
||||
| `ASCD`/`ASCC` <- `GetAnaliticByGrupUtilizatori` | Ce intoarce pe `NO_DATA_FOUND`, e acceptabil? | **Confirmat `NULL`** (corp citit `:16704-16723`: `lcAcont` declarat fara valoare implicita, `EXCEPTION WHEN NO_DATA_FOUND THEN NULL;`, `RETURN lcAcont`). Acceptabil — e exact fallback-ul deja folosit azi, necondiționat, pe ramurile de aviz (`:7416-7417,7421-7422`) si `ACT_TEMP.ASCD/ASCC` sunt nullable (confirmat DDL, `canal_cont_venit_fara_politica.md` DDL section). Nu e un risc nou. |
|
||||
| Articol "compus" (`V_COMPUS=1`) lasat in afara scopului | Poate un articol ales ad-hoc din nomenclator fi compus? | **NU — exclus prin structura schemei, nu prin presupunere.** Interogat direct `ALL_VIEWS.VCRM_POLITICI_PRET_ART`: coloana `COMPUS` citita de `contabilizeaza_articol` (`SELECT COMPUS, ID_POL_ART ... FROM VCRM_POLITICI_PRET_ART`, `:7279-7283`) e definita in view ca `CASE WHEN PA.ID_POL_ART IN (SELECT DISTINCT ID_PACHET FROM CRM_PACHETE_ARTICOLE WHERE STERS=0) THEN 1 ELSE 0 END` — o proprietate a **perechii (articol, politica)**, identificata prin `ID_POL_ART` (cheia surogat a randului din `CRM_POLITICI_PRET_ART`). O linie fara politica **nu are** un `ID_POL_ART` — deci intrebarea "e articolul compus" nu se poate pune structural pe aceasta ramura. (`NOM_ARTICOLE.COMPUS` exista ca alta coloana, aliasata `art_compus` in acelasi view, dar **nu e citita de `contabilizeaza_articol`** — irelevanta aici.) Excluderea din design e corecta, nu o gaura. |
|
||||
| `RETURN V_INCASAT_CALCUL` | Se calculeaza corect pe ramura noua sau intoarce 0? | **Corect, prin constructie.** Design-ul specifica explicit aceeasi acumulare ca azi (`V_INCASAT_CALCUL := V_INCASAT_CALCUL + scrie_nota(...)`, apoi `- scrie_discount(...)`), cu `V_INCASAT_CALCUL` initializat `0` la declaratie (`:7178`), inainte de `IF`-ul care selecteaza ramura — identic cu azi. Niciun risc de `RETURN 0` gasit. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Hardcodarile `SCD='4111'` / `CU_TVA=1` — exista o sursa mai buna?
|
||||
|
||||
Cautare directa in tot `PACK_FACTURARE` pentru orice sursa alternativa care nu trece prin
|
||||
`NOTE_CONTABILE`: config de firma (`getoptiunefirma('CONT...')`), cont implicit pe partener/client
|
||||
(`PARTENERI.CONT*`), flag de scutire TVA pe articol/client (`SCUTIT`, `EXCEPTAT`) — **zero rezultate
|
||||
pentru toate cele trei cautari**. Singurul camp inrudit gasit, `ACT_TEMP.NEIMPOZAB`, e folosit in alt
|
||||
scop (raportare sume neimpozabile), nu ca sursa pentru `CU_TVA`. **Concluzie: nu exista o sursa mai
|
||||
buna in cod — hardcodarea (sau parametrul explicit trimis de VFP) ramane singura optiune.** Asta
|
||||
intareste recomandarea deja facuta de autor: **de decis explicit cu Marius, nu de dedus din date**,
|
||||
mai ales pentru `CU_TVA` dupa gasirea de la punctul 2b (efectul via `nproc_tva_max` nu mai e
|
||||
"pur cosmetic").
|
||||
|
||||
---
|
||||
|
||||
## 5. Completare: `goExecutor.oExecuta` si cursorul de verificare — fals pozitiv, nu bug
|
||||
|
||||
Verdict: **fals pozitiv, confirmat cu argumente, nu doar presupus.** `oExecuta` (funcție-wrapper,
|
||||
`COMUN\programe\oproceduri_comune.prg:121-159`) deleagă la `oExecute` (`:173-504`), care la rândul ei
|
||||
face `SQLExec(lnHandle, lcSql, lcCursor)` (`:330`) — **`lcSql` e folosit exact cum a fost construit de
|
||||
apelant, fără nicio rescriere care să adauge un bind lipsă.** Textul construit în
|
||||
`COMUN\clase\ofacturare.vc2:14345-14359` (și identic la `:14373-14387`, `:18343-18371`) e sintaxă
|
||||
ODBC escape `{call pack_facturare.scrie_factura2(...)}`, cu **16** argumente poziționale (15 `IN` +
|
||||
`?@poDate.nid_vanzare` pentru `V_ID_VANZARE OUT NUMBER`, al 16-lea parametru declarat), paranteza se
|
||||
închide imediat după — **al 17-lea parametru, `V_CURSOR_VERIFICARE OUT pack_facturare.cursor_facturare`
|
||||
(spec `PACK_FACTURARE:656`), nu are niciun placeholder în text, nici legat, nici nelegat.** Asta e
|
||||
**exact tiparul cunoscut al driverului ODBC Oracle pe care VFP îl folosește prin `SQLExec`**: cand
|
||||
ultimul parametru declarat al unei proceduri e un `REF CURSOR OUT`, driverul îl detectează din
|
||||
catalogul Oracle (nu din textul apelului) și întoarce automat rândurile lui ca *result set* al
|
||||
apelului — **al treilea argument al `SQLExec`/`oExecute` (numele de cursor VFP, aici `lcCursorVerificare`)
|
||||
e exact mecanismul de captare a acelui result set**, fără sa fie nevoie de bind explicit. Codul imediat
|
||||
după apel (`ofacturare.vc2:14394-14397`, `llReturn = goExecutor.oExecuta(lcSql,lcCursorVerificare)`
|
||||
urmat de `If Reccount(lcCursorVerificare)>0`) tratează cursorul ca fiind deja populat cu rânduri —
|
||||
comportament incompatibil cu un apel eșuat sau cu un parametru nelegat (care ar da eroare Oracle,
|
||||
nu un cursor gol interpretabil). **Nu pot confirma mecanismul din interiorul driverului însuși** (e
|
||||
extern codebase-ului, verificabil doar prin comportament) — dar dovada indirectă e puternică: același
|
||||
tipar apare identic în toate cele 4 locuri de apel găsite, neschimbat de-a lungul mai multor versiuni
|
||||
(`v 2.0.13` -> `v 2.0.93`, comentarii de istoric vizibile în cod), iar ecranul de verificare (funcție
|
||||
activă, folosită la fiecare emitere) depinde de acest cursor populat — dacă apelul ar eșua silențios,
|
||||
ecranul de verificare n-ar arăta niciodată note propuse, ceea ce ar fi fost observat imediat. **Concluzie
|
||||
pentru plan: anomalia nu există — nu trebuie tratată ca risc pe cele 7 produse.**
|
||||
|
||||
## Ce ramane neverificat
|
||||
|
||||
- Comportamentul exact al ramurii `ntip=4`/`scrie_fact_aviz_custodie` in scenariul fallback (punctul
|
||||
1) — argumentat ca improbabil, nu testat/exclus explicit in design.
|
||||
- Daca vreun raport Oracle activ presupune `ACT_TEMP.ID_VENCHELT`/`ID_SECTIE` populate pe liniile de
|
||||
venit (relevant doar daca sesiunea nu seteaza `nid_venchelt`/`nid_sectie_stoc`) — in afara scopului
|
||||
acestei verificari (cod PL/SQL only).
|
||||
- Suprafata de regresie la nivelul apelantilor `adauga_articol_factura` din restul suitei — explicit
|
||||
lasata altui agent.
|
||||
272
docs/cercetare/rec_cale_vanzari_detalii.md
Normal file
272
docs/cercetare/rec_cale_vanzari_detalii.md
Normal file
@@ -0,0 +1,272 @@
|
||||
# Cercetare: calea VFP->Oracle la emitere si calea propusa pentru editare (#6, punctul E.4)
|
||||
|
||||
Sursa: interogari SELECT proaspete pe `MARIUSM_AUTO@ROA_CENTRAL` (08.08.2026), acelasi mediu si
|
||||
aceeasi versiune de baza ca in `rec_s5_oracle_vanzari.md` (nu s-a schimbat nimic intre cele doua
|
||||
cercetari din aceeasi zi). Numerele de linie PL/SQL de mai jos sunt dintr-un export propriu, facut
|
||||
in aceasta sesiune, din `all_source` pentru `PACK_FACTURARE` (schema/pachet identic cu cel din
|
||||
raportul anterior). Partea VFP e din cache-ul text `.vc2` din proiect (nu binarul).
|
||||
|
||||
Aceasta cercetare inchide golul lasat de `rec_s5_oracle_vanzari.md`, sectiunea E.4: cum ajung
|
||||
liniile facturii in Oracle la emitere si care e calea corecta pentru editare.
|
||||
|
||||
## 1. Calea de azi, capat la capat
|
||||
|
||||
### 1.1 VFP: cine populeaza `VANZARI_DETALII_TEMP`
|
||||
|
||||
Raspuns: **Oracle**, prin apeluri per-linie facute din VFP, nu VFP direct prin INSERT.
|
||||
|
||||
- `frm_facturare_articole.do_scrie_articole` (`COMUN\clase\ofacturare.vc2:13967-14195`):
|
||||
- `:13981-13999` cheama `pack_facturare.initializeaza_date_factura(...)` (antetul documentului,
|
||||
tine minte parametrii in stare de sesiune pe pachet: `pack_facturare.ntip`, `nid_sucursala`,
|
||||
`nid_util` etc. — folosite mai jos de `adauga_articol_factura`).
|
||||
- `:14036-14115`: `SELECT crsfactura` -> `SCAN`, cate un `Scatter Name poArt MEMO` per linie, apoi
|
||||
pentru fiecare linie fara rata de contract (`ELSE` la `:14051`) construieste text PL/SQL literal
|
||||
(nu `{call}` cu parametri legati) si apeleaza
|
||||
`pack_facturare.adauga_articol_factura(id_temp, id_articol, serie, explicatie, id_pol,
|
||||
id_gestiune, pret_achizitie, pretd, id_valutad, pret, id_valuta, cu_tva, gestionabil,
|
||||
cantitate, discount, cont, curs, multiplicator, id_jtva_coloana, id_part_rez, id_lucrare_rez,
|
||||
pretv_orig, id_set_fact, id_ctr, gnIdUtil, taxcode, lot)` (`:14069-14091`), executat imediat
|
||||
prin `goExecutor.oExecute(lcSql)` (`:14105`) — **un apel Oracle per linie de factura**, in
|
||||
bucla, nu un singur apel cu tot cursorul.
|
||||
- Pentru liniile din seturi: `pack_facturare.initializeaza_seturi_temp` + un apel
|
||||
`pack_facturare.adauga_articol_set(...)` per linie de set (`:14117-14154`), scrie in
|
||||
`VANZARI_SETURI_TEMP`.
|
||||
- `frm_facturare_articole.do_scrie_factura` (`:14197-14554`): dupa ce toate liniile au fost urcate
|
||||
in TEMP prin apelurile de mai sus, apeleaza finalizarea documentului —
|
||||
`pack_facturare.scrie_factura2(...)` (facturi, `:14345`/`:14373`) sau
|
||||
`pack_facturare.scrie_proforma(...)` (`:14286`) sau `scrie_factura_avize(...)`/
|
||||
`scrie_factura_avize_retur(...)` dupa tipul documentului — **un singur apel**, fara sa mai
|
||||
transmita liniile (ele sunt deja in `VANZARI_DETALII_TEMP`, populata de bucla anterioara, in
|
||||
ACEEASI sesiune/tranzactie Oracle).
|
||||
|
||||
### 1.2 Oracle: `adauga_articol_factura` — nu doar INSERT, RECALCULEAZA din sursa originala
|
||||
|
||||
Verificat sursa completa a procedurii publice `pack_facturare.adauga_articol_factura` (semnatura cu
|
||||
`V_ID_TEMP` ca prim parametru, cea apelata de VFP la `:14069`) — **nu** e un simplu INSERT cu
|
||||
valorile primite de la VFP. Ramifica pe `pack_facturare.ntip` (starea de sesiune setata la
|
||||
`initializeaza_date_factura`) si **re-deriva** `V_PRET`, `V_PROC_TVAV`, `V_ID_VALUTA`,
|
||||
`V_PRETURI_CU_TVA`, `V_IN_STOC` direct din documentul-sursa:
|
||||
- `ntip IN (3,21,28,42,47)` (facturare din comenzi): `SELECT ... FROM COMENZI_ELEMENTE ...`
|
||||
- `ntip = 4` (facturare din avize): `SELECT ... FROM VANZARI_DETALII ...` (avizul deja scris)
|
||||
- `ntip = 45` (restaurant): calcul din `JTVA_COLOANE` + `CRM_POLITICI_PRET_ART`
|
||||
- `V_OPT_FACTURARE = 3` (contract): `SELECT ... FROM CTR_ARTICOLE ...`
|
||||
- fallback (`ELSE`): doar cota TVA din `JTVA_COLOANE`, restul (`V_PRET`, `V_ID_VALUTA`,
|
||||
`V_PRETURI_CU_TVA`, `V_IN_STOC`) preluate ca atare din parametrii transmisi de VFP.
|
||||
|
||||
Abia dupa acest bloc `CASE` urmeaza INSERT-ul propriu-zis:
|
||||
|
||||
```sql
|
||||
INSERT INTO VANZARI_DETALII_TEMP
|
||||
(ID_TEMP, ID_ARTICOL, SERIE, LOT, EXPLICATIA, ID_POL, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
|
||||
PRET, PRET_CU_TVA, PROC_TVAV, CANTITATE, DISCOUNT_UNITAR, ID_GESTIUNE, CONT, ID_VALUTA,
|
||||
CURS, MULTIPLICATOR, ID_JTVA_COLOANA, IN_STOC, ID_VANZARE_SET, ID_PART_REZ, ID_LUCRARE_REZ,
|
||||
PRETV_ORIG, CUSTODIE, ID_CTR, ID_UTIL, TAXCODE)
|
||||
VALUES (...)
|
||||
```
|
||||
|
||||
(am confirmat integral corpul, INSERT-ul e ultimul bloc din procedura, imediat dupa `END CASE`).
|
||||
|
||||
**Consecinta directa pentru #6**: `adauga_articol_factura` NU poate fi reutilizata ca atare pentru
|
||||
editare — depinde de starea de sesiune `pack_facturare.ntip`/`clistaid`/`id_ctr` care descrie
|
||||
DOCUMENTUL SURSA de la emitere (comanda/aviz/contract), stare care nu exista si nu are sens la o
|
||||
editare ulterioara a facturii deja emise. Orice procedura noua pentru editare trebuie sa scrie
|
||||
direct in `VANZARI_DETALII` (tabela reala), nu prin acest drum.
|
||||
|
||||
Exista si `sterge_articol_factura(V_ID_TEMP, V_ID_UTIL)` — `DELETE FROM VANZARI_DETALII_TEMP WHERE
|
||||
ID_TEMP = V_ID_TEMP` — folosita doar cat timp factura e in curs de compunere (inainte de emitere),
|
||||
nu dupa.
|
||||
|
||||
### 1.3 Oracle: `scrie_factura2` -> `finalizeaza_factura` -> `scrie_in_vanzari` — trecerea TEMP -> real
|
||||
|
||||
`scrie_factura2` (PACK_FACTURARE, corpul activ, nu varianta comentata din cod):
|
||||
- `:70` `pack_contafin.sterge_temp_actrul()`, seteaza `pack_facturare.ntotftva/ntottva` din
|
||||
parametri (valori calculate in VFP).
|
||||
- `:84` `SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP` — citeste TOATA tabela
|
||||
temp intr-un array PL/SQL, fara filtru pe sesiune (GTT-ul e oricum izolat per sesiune/tranzactie).
|
||||
- Bucla pe `tab_detalii`, ramificata tot pe `ntip` (transfer intre subunitati, restaurant etc.) —
|
||||
pentru cazul standard de factura apeleaza mai departe spre `pack_facturare.finalizeaza_factura`.
|
||||
|
||||
`finalizeaza_factura` (nu comentata):
|
||||
```sql
|
||||
pack_facturare.initializeaza_scriere_actrul(V_DATAORA);
|
||||
pack_facturare.scrie_in_vanzari(V_DISCOUNT_FACTURA, V_ID_DELEGAT, V_ID_MASINA, V_ID_FACTURARE,
|
||||
V_LISTARE_DETALIATA, V_DATAORA_EXP, V_ID_AGENT, V_TEXT_ADITIONAL, pack_facturare.nid_vanzare);
|
||||
pack_facturare.finalizeaza_scriere_actrul();
|
||||
-- update vanzari set id_fact = ... (completare id_fact dupa scrierea in contabilitate)
|
||||
```
|
||||
|
||||
`scrie_in_vanzari` — gasit exact mecanismul care lipsea din raportul anterior:
|
||||
- `INSERT INTO VANZARI (...) VALUES (...) RETURNING ID_VANZARE INTO pack_facturare.nid_vanzare`
|
||||
(randul parinte, un rand per document din bucla pe `tab_vanz`, tabela interna cu documentele de
|
||||
scris — cazul normal e un singur document).
|
||||
- `pack_facturare.scrie_cursuri(pack_facturare.nid_vanzare)`.
|
||||
- **Trecerea efectiva TEMP -> real**:
|
||||
```sql
|
||||
INSERT /*+ APPEND */ INTO VANZARI_DETALII
|
||||
(ID_VANZARE, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD, ID_VALUTAD,
|
||||
PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA, ID_JTVA_COLOANA)
|
||||
SELECT pack_facturare.nid_vanzare, ID_ARTICOL, SERIE, ID_POL, CANTITATE, PRET_ACHIZITIE, PRETD,
|
||||
ID_VALUTAD, PRET, PROC_TVAV, DISCOUNT_UNITAR, ID_GESTIUNE, ID_VALUTA, CONT, PRET_CU_TVA,
|
||||
ID_JTVA_COLOANA
|
||||
FROM VANZARI_DETALII_TEMP
|
||||
WHERE ID_COMANDA = tab_vanz(i).id_comanda AND NUMAR_ACT = tab_vanz(i).numar_act
|
||||
ORDER BY ID_TEMP;
|
||||
```
|
||||
Cheia de potrivire intre randul nou din `VANZARI` si liniile lui din TEMP e
|
||||
`(ID_COMANDA, NUMAR_ACT)` — nu `ID_TEMP` direct — pentru ca un singur apel poate scrie mai multe
|
||||
documente `VANZARI` deodata (facturare pe mai multe comenzi/numere de act simultan); fiecare
|
||||
document isi ia doar liniile care se potrivesc.
|
||||
- Coloanele COPIATE in `VANZARI_DETALII` sunt un subset din `VANZARI_DETALII_TEMP` (16 coloane) —
|
||||
`ID_TEMP`, `ID_CTR`, `ID_UTIL`, `TAXCODE`, `LOT`, `ID_VANZARE_SET`, `ID_PART_REZ`,
|
||||
`ID_LUCRARE_REZ`, `PRETV_ORIG`, `CUSTODIE`, `NUMAR_ACT`, `ID_COMANDA`, `ID_GESTIUNE_DEST`
|
||||
raman DOAR in TEMP, nu se copiaza in tabela reala (`VANZARI_DETALII` nu are coloanele `ID_TEMP`,
|
||||
`NUMAR_ACT`, `ID_COMANDA` etc. — confirmat din `all_tab_columns`, vezi 2.2).
|
||||
- Urmeaza blocul de agregare (`SELECT INTO` din `VANZARI_DETALII_TEMP`, deja documentat in
|
||||
`rec_s5_oracle_vanzari.md` A.3-A.4) si `UPDATE VANZARI SET total_fara_tva=..., ...` cu totalurile
|
||||
denormalizate.
|
||||
|
||||
**PK-ul `VANZARI_DETALII.ID_VANZARE_DET`** se genereaza automat: trigger `TRG_VANZARI_DET_BEFOINS`
|
||||
(`BEFORE INSERT ... FOR EACH ROW`, verificat prin `dbms_metadata.get_ddl`) face exclusiv
|
||||
`SELECT SEQ_VANZARI_DETALII.NEXTVAL INTO :NEW.ID_VANZARE_DET FROM DUAL` — nu seteaza alte coloane.
|
||||
Deci **orice INSERT nou in `VANZARI_DETALII`** (inclusiv unul scris pentru #6, la adaugare de linie
|
||||
noua la editare) primeste automat PK-ul corect fara sa fie nevoie de secventa apelata manual din
|
||||
codul de editare.
|
||||
|
||||
## 2. Natura tabelei temp si structura
|
||||
|
||||
### 2.1 `VANZARI_DETALII_TEMP` — Global Temporary Table, `ON COMMIT DELETE ROWS`
|
||||
|
||||
Confirmat din `all_tables`:
|
||||
|
||||
| TABLE_NAME | TEMPORARY | DURATION |
|
||||
|---|---|---|
|
||||
| VANZARI | N | — |
|
||||
| VANZARI_DETALII | N | — |
|
||||
| VANZARI_DETALII_TEMP | **Y** | **SYS$TRANSACTION** |
|
||||
| VANZARI_SETURI_TEMP | **Y** | **SYS$TRANSACTION** |
|
||||
|
||||
`DURATION = SYS$TRANSACTION` inseamna GTT cu `ON COMMIT DELETE ROWS` — randurile dispar la commit
|
||||
(sau rollback), nu doar la sfarsit de sesiune. **Consecinta directa**: orice solutie care ar
|
||||
reutiliza `VANZARI_DETALII_TEMP` pentru editare (#6) trebuie sa umple tabela SI sa consume
|
||||
rezultatul in ACEEASI tranzactie — nu poate fi umpluta intr-un pas si citita in altul, ca in fluxul
|
||||
de emitere unde umplere (bucla `adauga_articol_factura`) si consum (`scrie_factura2`) se intampla
|
||||
deja in aceeasi conexiune/tranzactie, inainte de commit.
|
||||
|
||||
### 2.2 Coloane: `VANZARI_DETALII` vs `VANZARI_DETALII_TEMP`
|
||||
|
||||
`VANZARI_DETALII` (35 coloane, din `all_tab_columns`) — cheie primara `ID_VANZARE_DET` (NOT NULL,
|
||||
generata din secventa prin trigger), FK logic `ID_VANZARE` (NOT NULL). Coloane relevante pentru
|
||||
editare: `PRET` (NOT NULL), `CANTITATE`, `PRET_CU_TVA`, `DISCOUNT_UNITAR`, `PROC_TVAV`, `STERS`
|
||||
(NOT NULL), `VALIDAT`/`DATAORA_VALID`/`ID_UTIL_VALID`, `ID_UTILS`/`DATAORAS` (audit),
|
||||
`DIFERENTA` (NOT NULL), `CUSTODIE`/`DESCARCAT` (NOT NULL) — ultimele patru nu au valoare implicita
|
||||
vizibila in trigger, deci probabil `DEFAULT` la nivel de coloana (nu s-a verificat separat, nu era
|
||||
in scope).
|
||||
|
||||
`VANZARI_DETALII_TEMP` (28 coloane) — **fara PK real** (`ID_TEMP` e generat in VFP, folosit doar ca
|
||||
identificator temporar de linie in cadrul sesiunii curente de compunere a facturii), **fara**
|
||||
`ID_VANZARE`/`ID_VANZARE_DET`/`STERS`/`VALIDAT` (nu se aplica inainte de a exista documentul
|
||||
parinte). Are in schimb coloane specifice etapei de compunere, care NU exista in tabela reala:
|
||||
`ID_TEMP`, `NUMAR_ACT`, `ID_COMANDA`, `ID_GESTIUNE_DEST`, `ID_UTIL` (vs `ID_UTILS` in cea reala).
|
||||
|
||||
Coloanele comune folosite de editare (S4): `ID_ARTICOL`, `PRET`, `CANTITATE`, `PRET_CU_TVA`,
|
||||
`DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_GESTIUNE`, `CONT`, `ID_VALUTA`, `CURS` (doar in TEMP; in
|
||||
`VANZARI_DETALII` nu exista `CURS` per linie — cursul e doar pe document, in `VANZARI_CURSURI`/
|
||||
`VANZARI.CURS`), `ID_JTVA_COLOANA`, `SERIE`, `EXPLICATIE`/`EXPLICATIA` (nume diferit!), `TAXCODE`,
|
||||
`LOT`.
|
||||
|
||||
## 3. Stergerea si adaugarea de linii
|
||||
|
||||
### 3.1 Stergere — mecanism EXISTENT azi doar la nivel de document intreg, NU per linie
|
||||
|
||||
Cautat explicit orice `UPDATE VANZARI_DETALII SET STERS` in `PACK_FACTURARE` — gasite doar doua
|
||||
locuri, ambele sterg TOATE liniile unui document, niciodata o singura linie:
|
||||
|
||||
- `sterge_factura(V_ID_VANZARE, ...)`:
|
||||
`UPDATE VANZARI_DETALII SET STERS = V_STERS, ID_UTILS = V_ID_UTIL, DATAORAS = V_DATAORA WHERE
|
||||
ID_VANZARE = V_ID_VANZARE AND STERS = V_NESTERS` (V_STERS variaza 0/1 dupa tipul documentului,
|
||||
cazul normal fiind 1).
|
||||
- `sterge_proforma(V_ID_VANZARE, ...)`: acelasi tipar, exclusiv pe proforme.
|
||||
|
||||
**Nu exista azi un mecanism Oracle sau VFP de stergere per-linie in `VANZARI_DETALII`** — orice
|
||||
"stergere de linie la editare" ceruta de S4/S4b e functionalitate NOUA, de adaugat la #6, nu o
|
||||
reutilizare a ceva existent.
|
||||
|
||||
Exista insa un precedent apropiat de UPDATE punctual per linie, util ca sablon:
|
||||
`modifica_explicatie_articol(V_ID_VANZARE_DET, V_EXPLICATIE, V_ID_UTIL, V_TAXCODE)` —
|
||||
`UPDATE VANZARI_DETALII SET EXPLICATIE = V_EXPLICATIE, TAXCODE = V_TAXCODE WHERE ID_VANZARE_DET =
|
||||
V_ID_VANZARE_DET` — exact modelul de "UPDATE tintit prin `goExecutor`" mentionat ca varianta in
|
||||
planul S5/S6, deja folosit azi din `frm_modifica_articol_factura` (mostenire de la #7, vezi
|
||||
`plan_06_editare_factura.md`).
|
||||
|
||||
### 3.2 Adaugare de linie noua — nu cere nimic special in plus fata de un INSERT direct
|
||||
|
||||
`ID_VANZARE_DET` vine automat din `SEQ_VANZARI_DETALII` prin trigger la orice `INSERT INTO
|
||||
VANZARI_DETALII`, indiferent de cine face insert-ul (nu doar fluxul de emitere) — vezi 1.3. Deci
|
||||
adaugarea unei linii noi la editare **nu** cere obtinerea manuala a unui ID din secventa; e suficient
|
||||
un `INSERT INTO VANZARI_DETALII (ID_VANZARE, ID_ARTICOL, PRET, CANTITATE, PRET_CU_TVA, ...) VALUES
|
||||
(...)` (fara `ID_VANZARE_DET` in lista de coloane) intr-o procedura noua, apelata din
|
||||
`finalizeaza_modificare_nota` alaturi de recalculul de totaluri propus in `rec_s5_oracle_vanzari.md`
|
||||
punctul B.
|
||||
|
||||
## 4. Propunerea pentru S4/S5/S6 — cum trimite editarea liniile modificate in Oracle
|
||||
|
||||
### Optiuni comparate
|
||||
|
||||
**A. Refolosirea `VANZARI_DETALII_TEMP` + o "mini scrie_in_vanzari" pentru editare** — ar insemna ca
|
||||
formularul de editare (S4) sa populeze TEMP la fel ca la emitere (apeluri per linie), apoi o
|
||||
procedura noua sa faca INSERT-urile/UPDATE-urile in `VANZARI_DETALII` din TEMP, in aceeasi
|
||||
tranzactie (impusa de `ON COMMIT DELETE ROWS`, vezi 2.1). Risc: reproduce complexitatea lui
|
||||
`adauga_articol_factura` (ramificare pe `ntip`/sursa document) fara sa aiba sens la editare — acolo
|
||||
nu exista comanda/aviz/contract "curent" din care sa se re-deriva pretul; ar trebui un mod nou,
|
||||
simplificat, de populare a TEMP doar pentru editare, ceea ce complica inutil un cod deja incarcat.
|
||||
Risc mediu-mare de regresie pe calea de emitere daca vreo modificare atinge accidental
|
||||
`adauga_articol_factura`/`scrie_in_vanzari` partajate.
|
||||
|
||||
**B. UPDATE/INSERT/soft-DELETE punctual direct in `VANZARI_DETALII`, prin `goExecutor`, dintr-o
|
||||
procedura noua dedicata editarii** — fara sa treaca deloc prin `VANZARI_DETALII_TEMP`. Model deja
|
||||
existent si folosit: `modifica_explicatie_articol` (UPDATE tintit pe `ID_VANZARE_DET`). Pentru cele
|
||||
trei operatii cerute de S4/S4b:
|
||||
- **Editare cantitate/pret/`pret_cu_tva`** pe o linie existenta: `UPDATE VANZARI_DETALII SET
|
||||
CANTITATE=..., PRET=..., PRET_CU_TVA=..., DISCOUNT_UNITAR=... WHERE ID_VANZARE_DET = :id`.
|
||||
- **Stergere linie**: `UPDATE VANZARI_DETALII SET STERS=1, ID_UTILS=:util, DATAORAS=SYSDATE WHERE
|
||||
ID_VANZARE_DET = :id` — acelasi tipar ca `sterge_factura`, dar tintit pe o singura linie (nou,
|
||||
nu exista azi, vezi 3.1).
|
||||
- **Adaugare linie noua**: `INSERT INTO VANZARI_DETALII (ID_VANZARE, ID_ARTICOL, PRET, CANTITATE,
|
||||
PRET_CU_TVA, DISCOUNT_UNITAR, PROC_TVAV, ID_GESTIUNE, CONT, ID_VALUTA, ID_JTVA_COLOANA, SERIE,
|
||||
EXPLICATIE, TAXCODE, LOT) VALUES (...)` — PK automat din trigger (vezi 3.2).
|
||||
Apoi, in ACEEASI tranzactie (deschisa deja de `do_deschide_tranzactie` in fluxul de editare a
|
||||
notei, cf. `rec_s5_oracle_vanzari.md` B "Idempotenta si tranzactionalitate"), se cheama procedura
|
||||
de recalcul a totalurilor propusa in `rec_s5_oracle_vanzari.md` (`recalculeaza_totaluri_vanzari`),
|
||||
care citeste direct din `VANZARI_DETALII WHERE ID_VANZARE=:id AND STERS=0` — **fara nicio
|
||||
dependenta de `VANZARI_DETALII_TEMP`**.
|
||||
|
||||
### Recomandare: **Varianta B**
|
||||
|
||||
Argumentul principal e riscul de regresie asupra emiterii: Varianta A ar obliga fie la extinderea
|
||||
lui `adauga_articol_factura` cu o ramura noua "editare" (cod partajat cu emiterea, risc direct pe
|
||||
calea critica), fie la duplicarea partiala a logicii lui in altă procedura (risc de divergenta,
|
||||
exact problema pe care #8 a corectat-o deja pentru calculul de totaluri). Varianta B nu atinge deloc
|
||||
`adauga_articol_factura`/`scrie_in_vanzari`/`VANZARI_DETALII_TEMP` — cod nou, izolat, apelat doar
|
||||
din calea de editare (`finalizeaza_modificare_nota`), cu acelasi profil de risc "mic" motivat deja
|
||||
pentru `recalculeaza_totaluri_vanzari` in `rec_s5_oracle_vanzari.md`. In plus, B se potriveste
|
||||
natural cu S4b (verificare/sincronizare explicita, nu silentioasa): fiecare rand modificat/sters/
|
||||
adaugat poate fi tratat ca o comanda separata, usor de enumerat utilizatorului inainte de aplicare
|
||||
("linia X: cantitate 5 -> 8, pret 10 -> 12"), pe cand Varianta A ar produce un singur bloc opac de
|
||||
recalcul din care nu se pot extrage usor liniile individuale schimbate.
|
||||
|
||||
**Observatie tehnica pentru implementare**: la editarea preturilor, NU se recalculeaza `V_PROC_TVAV`
|
||||
din `JTVA_COLOANE`/comanda/contract ca la emitere (asta ar reintroduce dependenta de sursa
|
||||
documentului, respinsa mai sus) — cota de TVA ramane cea deja persistata pe linie
|
||||
(`VANZARI_DETALII.PROC_TVAV`), coerent cu felul in care `pret_cu_tva`/`proc_tvav` sunt deja tratate
|
||||
ca date proprii ale liniei si nu recalculate la fiecare atingere (cf. #7).
|
||||
|
||||
## 5. Ce ramane neconfirmat / in afara scopului acestei cercetari
|
||||
|
||||
- Coloanele `STERS`, `VALIDAT`, `DIFERENTA`, `CUSTODIE`, `DESCARCAT` din `VANZARI_DETALII` sunt
|
||||
`NOT NULL` fara sa aiba valoare din trigger — probabil au `DEFAULT` la nivel de coloana
|
||||
(`all_tab_columns.data_default` nu a fost interogat, nu era necesar pentru concluziile de mai
|
||||
sus); de verificat explicit inainte de a scrie INSERT-ul de linie noua din Varianta B, ca sa nu
|
||||
fie nevoie sa le populeze manual.
|
||||
- Valorile implicite exacte pentru `DEFAULT` (daca exista) pe coloanele NOT NULL de mai sus.
|
||||
- Comportamentul `VANZARI_SETURI_TEMP`/liniile din seturi la editare (#6 nu pare sa acopere editarea
|
||||
seturilor, doar articolele individuale — de clarificat cu Marius daca seturile sunt in scope).
|
||||
245
docs/cercetare/rec_d42_efactura.md
Normal file
245
docs/cercetare/rec_d42_efactura.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# Implementarea deciziei 42 — articole needitabile pe factura trimisa in eFactura
|
||||
|
||||
Scris 10.08.2026. Implementare + testare + write-back verificat. **ZERO commit** (git/svn),
|
||||
conform interdictiei primite.
|
||||
|
||||
> ## COMPLETARE, 10.08.2026 ora ~12:55 — discountul de antet intra sub garda
|
||||
>
|
||||
> Agentul care a scris acest raport a apucat sa faca **modificarea de cod si write-back-ul**, apoi a
|
||||
> cazut pe limita de sesiune **inainte de verificare**. Blocul de mai jos e scris de orchestrator,
|
||||
> care a preluat si a dus verificarea la capat.
|
||||
>
|
||||
> **Decizia lui Marius**: `txtDiscountArt` (discountul de antet) **se blocheaza si el** cand documentul
|
||||
> e trimis in eFactura — schimba totalurile unui document deja depus la ANAF, chiar daca nu atinge
|
||||
> nicio linie.
|
||||
>
|
||||
> **Codul**: `omodificari.vc2:14813` — `This.pgfArticole.PAGE3.txtDiscountArt.ReadOnly =
|
||||
> This.lArticoleReadOnly`, prin acelasi flag, fara mecanism nou.
|
||||
>
|
||||
> **A doua cale de scriere — cautata si exclusa**: `txtDiscountArt.Valid` (`:16592`) doar cheama
|
||||
> `Thisform.ActualizeazaBaraTotaluri()`, deci **recalculeaza afisajul, nu scrie discountul nicaieri**.
|
||||
> Nu exista buton sau apel programatic care sa-l seteze ocolind caseta, deci garda dubla care a fost
|
||||
> necesara la butoanele de articole (`Click`) **nu isi are rostul aici**. Salvarea propriu-zisa trimite
|
||||
> discountul ca parametru catre `recalculeaza_totaluri_vanzari` (decizia 41), luand valoarea din caseta.
|
||||
>
|
||||
> **Verificat de orchestrator pe disc, dupa caderea agentului**:
|
||||
> - **Write-back complet**, dovedit prin reconversie binar -> text in cache temporar si comparatie
|
||||
> **octet cu octet**: identic, 540712 octeti. Cens de octeti `2 aa . 2 e3 . 2 fe`, zero `EF BF BD`.
|
||||
> - **Regresia rerulata integral pe starea de pe disc**, dupa write-back (binar 12:33:09):
|
||||
> `test_page3_articole` **14/2** (artefactul headless cunoscut), `test_adauga_linie_articol` **20/0**,
|
||||
> `test_adauga_linie_valuta` **16/0**, `test_ui_sterge_linie` **8/0**, `test_verdict_act_rul` **26/0**,
|
||||
> `test_incarca_vanzare_din_nota` **5/0**, `test_s5_validari_articole` **35/0**. Nicio regresie.
|
||||
> - **Acoperire noua**: `test_efactura_readonly.prg` extins cu doua asertii si rerulat —
|
||||
> **23 PASS / 0 FAIL** (de la 21). Cele doua noi: `caz A: txtDiscountArt.ReadOnly = .T.` pe documentul
|
||||
> real trimis in eFactura, si `caz B: txtDiscountArt.ReadOnly = .F. (neregresat)` pe cel netrimis.
|
||||
> Garda e verificata deci **in ambele sensuri**, nu doar pe cazul pozitiv.
|
||||
> - Zero procese `vfp9.exe` ramase, zero commituri.
|
||||
>
|
||||
> **Ce NU e acoperit**: suita UI vizibila (`test_ui_efactura_readonly.prg`, 14/0) **nu a fost rerulata**
|
||||
> dupa adaugarea discountului — ea verifica `.When`-urile de celula, neatinse de aceasta completare,
|
||||
> deci riscul e mic, dar golul e declarat, nu ascuns.
|
||||
>
|
||||
> Diff-ul consolidat (COMUN + ROAGEST) e regenerat in diff aplicat (sters).
|
||||
|
||||
## Ce s-a schimbat, si unde
|
||||
|
||||
### `COMUN\clase\omodificari.vc2` (`frm_modific2024`)
|
||||
|
||||
- **Proprietate noua** `lArticoleReadOnly` (Boolean, implicit `.F.`): `*p:` la `:6827`, valoare
|
||||
implicita la `:6866`.
|
||||
- **`Show`** (`:14788-14827`): calculeaza flagul in acelasi bloc unde se determina
|
||||
`lAreArticoleVanzari`/`nIdVanzare`/`nTipVanzare` (adica doar cand `ofacturare_editare.prg` e
|
||||
incarcat si documentul are rand in `VANZARI`):
|
||||
`This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact,0))` (`:14798`). In blocul care
|
||||
pregateste `PAGE3`, aplica flagul: `cmdAdaugaArticol.Enabled`/`cmdStergeArticol.Enabled` =
|
||||
`!lArticoleReadOnly` (`:14811-14812`), `lblArticoleReadOnly.Visible = lArticoleReadOnly`
|
||||
(`:14813`). **`PAGE3` continua sa se pregateasca si sa se afiseze normal** —
|
||||
`PregatesteArticoleFacturaEditare`/`PageCount=3`/`grdArticoleFactura.Refresh()` raman neatinse,
|
||||
conform corectiei explicite a lui Marius (articolele raman vizibile).
|
||||
- **Control nou** `pgfArticole.PAGE3.lblArticoleReadOnly` (`ADD OBJECT` la `:12776`): eticheta
|
||||
discreta, `Visible=.F.` implicit, pozitionata pe randul butoanelor (`Top=4`, `Left=360`,
|
||||
`Width=390`) — la dreapta lui `cmdAdaugaArticol` (care se termina la `Left+Width=345`), deci
|
||||
**nu ia spatiu din grid** (gridul ramane la `Top=26`). Text: „Articole needitabile - factura
|
||||
trimisa in eFactura", `ForeColor RGB(180,120,0)` (aceeasi nuanta de atentionare folosita deja
|
||||
la verdictul ACT/RUL divergent).
|
||||
- **Garda pe butoane** (`cmdAdaugaArticol.Click` `:16488`, `cmdStergeArticol.Click` `:16531`):
|
||||
`OR Thisform.lArticoleReadOnly` adaugat la conditia de `RETURN` timpuriu — belt-and-suspenders
|
||||
fata de `Enabled=.F.`, pentru ca `Enabled` nu blocheaza un apel programatic al metodei `.Click()`.
|
||||
- **Garda pe celule** — cele patru `.When` care controleaza editabilitatea pe rand (mecanismul
|
||||
din S5): `cCantitateArt.Text1.When` (`:16549`), `cPretAchizitieArt.Text1.When`,
|
||||
`cPretArt.Text1.When` (`:16570`), `cPretCuTvaArt._checkbox1.When` (`:16587`) — toate primesc
|
||||
`Thisform.lArticoleReadOnly OR ...` (respectiv `!Thisform.lArticoleReadOnly AND ...` pe
|
||||
checkbox, unde conditia veche era inversa). Se construieste **peste** mecanismul de
|
||||
editabilitate per rand din S5 (`id_vanzare_set`/`id_vanzare_det`), nu-l inlocuieste.
|
||||
- **Notele/rulajele nu sunt atinse**: `Grid1` (nota contabila), `grdRulaje`/`grdRulajeObinv`
|
||||
(paginile 1/2) raman complet neschimbate — verificat explicit in test (`Grid1.ReadOnly` ramane
|
||||
`.F.` pe documentul din eFactura).
|
||||
|
||||
### `COMUN\programe\ofacturare_editare.prg`
|
||||
|
||||
Corectie necesara descoperita in timpul lucrului (vezi mai jos „Decizie/corectie luata pe
|
||||
parcurs"): `EsteInEFactura` se apeleaza cu `VANZARI.ID_FACT`, **nu** cu `id_vanzare` — sunt doua
|
||||
coloane distincte (verificat pe date: 0 potriviri `id_fact = id_vanzare` din 142 facturi tip=1).
|
||||
Contractul existent (`ofacturare_comun.vc2:3764`, neatins) apela deja `EsteInEFactura(lnIdFact)`
|
||||
cu `lnIdFact = crsfacturi.id_fact`. `tvanz` (populat de `IncarcaVanzareNota`) nu avea aceasta
|
||||
coloana.
|
||||
|
||||
- `CreeazaCursorTvanzGol` (linia 153 din fisier): adaugat `id_fact I NULL` la structura cursorului
|
||||
gol (fallback pe eroare Oracle/cod lipsa).
|
||||
- `IncarcaVanzareNota` (linia 177): adaugat `v.id_fact` la lista de coloane selectate din
|
||||
`VANZARI`. Restul interogarii (join, filtre) neatins.
|
||||
- Antetul fisierului (o singura intrare cumulativa, rescrisa): data actualizata la 10.08.2026,
|
||||
mentiunea `id_fact` adaugata la lista de campuri incarcate.
|
||||
|
||||
## Decizie/corectie luata pe parcurs — de raportat, nu era in briefing
|
||||
|
||||
Implementarea initiala folosea `EsteInEFactura(This.nIdVanzare)` (adica `id_vanzare`). Verificare
|
||||
pe `MARIUSM_AUTO`:
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) total, SUM(CASE WHEN id_fact = id_vanzare THEN 1 ELSE 0 END) equal_cnt
|
||||
FROM vanzari WHERE tip = 1 AND sters = 0;
|
||||
-- 142 total, 0 equal_cnt
|
||||
```
|
||||
|
||||
`id_fact` si `id_vanzare` sunt spatii de ID complet diferite (`id_fact` are valori de forma
|
||||
`8008013`, `id_vanzare` valori mici de forma `1013`) — cu `id_vanzare` gardarea nu s-ar fi
|
||||
declansat NICIODATA in productie (nicio coincidenta intamplatoare intre cele doua plaje). Corectat
|
||||
inainte de scrierea testelor, folosind exact coloana pe care o foloseste deja calea din
|
||||
`ofacturare_comun.vc2:3764` (`lnIdFact = crsfacturi.id_fact`).
|
||||
|
||||
## Write-back — verificat prin reconversie + diff, nu pe mtime
|
||||
|
||||
`txt2vcx.ps1 -AllowComun` rulat de trei ori (prima incercare a picat fidelity-check-ul din cauza
|
||||
ordinii gresite a blocului `ADD OBJECT` — proprietatile/obiectele dintr-o clasa `.vcx` trebuie in
|
||||
ordine STRICT alfabetica dupa nume, nu dupa ZOrder; a doua rulare a picat pentru ca lipsea
|
||||
corectia `id_fact`; **a treia rulare, OK**, fidelity check trecut).
|
||||
|
||||
Verificare **independenta** de fidelity-check-ul intern al `txt2vcx.ps1`: reconversie separata a
|
||||
binarului proaspat scris (`vcx2txt.ps1 -Source omodificari.vcx` intr-un cache temporar izolat) +
|
||||
`diff`/`cmp` octet cu octet fata de `.vc2`-ul din proiect:
|
||||
|
||||
```
|
||||
diff -q omodificari.vc2 <reconversie>/omodificari.vc2 -> IDENTIC
|
||||
cmp omodificari.vc2 <reconversie>/omodificari.vc2 -> BYTE-IDENTIC
|
||||
```
|
||||
|
||||
Text si binar sunt sincrone, dovedit, nu presupus.
|
||||
|
||||
## Cens de octeti (regula cp1250) — inainte/dupa, identic cu baseline
|
||||
|
||||
`omodificari.vc2` are diacritice cp1250 preexistente (tooltip-uri „Renunțare/Adăugare/Ștergere",
|
||||
octeti `0xFE`/`0xE3`/`0xAA`). **Baseline: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD`.**
|
||||
|
||||
Editarea celor doua proprietati noi (`*p:`/`*<PropValue>`) s-a facut initial cu tool-ul `Edit`
|
||||
(2 apeluri) — asta a **stricat** censul (`6 ef / 6 bf / 6 bd`, adica `EF BF BD` x2 aparitii x3
|
||||
octeti), exact capcana documentata (`Edit`/`Write` re-encodeaza tot fisierul la orice scriere,
|
||||
indiferent cat de mica). **Reparat** inainte de a continua: octetii corecti (`Renun[FE]are`,
|
||||
`Ad[E3]ugare`, `[AA]tergere`) preluati din `git cat-file blob HEAD:clase/omodificari.vc2`
|
||||
(varianta necorupta, comisa) si inlocuiti inapoi punctual. Toate editarile ulterioare (Show(),
|
||||
garzile pe butoane/celule, blocul `ADD OBJECT` al etichetei noi) s-au facut **pe octeti**, cu
|
||||
Perl (`<:raw`/`>:raw`, cautare/inlocuire literala `\Q...\E`, fara nicio decodare de encoding),
|
||||
tocmai ca sa nu se repete problema. **Cens final: `2 aa / 2 e3 / 2 fe`, zero `EF BF BD`** —
|
||||
identic cu baseline, verificat dupa fiecare grup de editari.
|
||||
|
||||
`ofacturare_editare.prg` nu are octeti `>=0x80` (cens gol inainte si dupa) — editat direct cu
|
||||
`Edit`, fara risc.
|
||||
|
||||
## Regresie — cifre numarate din loguri, toate DUPA ultima editare de cod
|
||||
|
||||
Ultima editare de cod: `omodificari.vc2` scris in binar la `11:37:23` (a treia rulare
|
||||
`txt2vcx.ps1`, cu corectia `id_fact`); `ofacturare_editare.prg` editat inainte de asta. Toate
|
||||
rularile de mai jos sunt **dupa** acel moment.
|
||||
|
||||
| Suita | Rezultat | Baseline | Stare |
|
||||
|---|---|---|---|
|
||||
| `test_page3_articole.prg` | 14 PASS / 2 FAIL | 14/2 | **neregresat** (cele 2 FAIL = artefactul headless cunoscut, ColumnCount/ReadOnly pe grid needitabil sub `-A -T`) |
|
||||
| `test_adauga_linie_articol.prg` | 20 PASS / 0 FAIL | 20/0 | **neregresat** |
|
||||
| `test_adauga_linie_valuta.prg` | 16 PASS / 0 FAIL | 16/0 | **neregresat** |
|
||||
| `test_ui_sterge_linie.prg` | 8 PASS / 0 FAIL | 8/0 | **neregresat** |
|
||||
| `test_verdict_act_rul.prg` | 26 PASS / 0 FAIL | 26/0 | **neregresat** |
|
||||
| `test_incarca_vanzare_din_nota.prg` | 5 PASS / 0 FAIL | 5/0 | **neregresat** |
|
||||
| `test_s5_validari_articole.prg` | 35 PASS / 0 FAIL | 35/0 | **neregresat** (linia care contine cuvantul "FAIL" e text descriptiv al unei ramuri moarte deja documentate, nu un esec real — `REZULTAT: 35 PASS / 0 FAIL`) |
|
||||
|
||||
Zero regresii pe toate cele sapte suite existente.
|
||||
|
||||
## Suite noi
|
||||
|
||||
### `COMUN\utile\Teste\editare_factura\test_efactura_readonly.prg` (headless) — **21 PASS / 0 FAIL**
|
||||
|
||||
Documente REALE, gasite prin interogare (nu inventate):
|
||||
- **Caz A — trimis in eFactura**: `id_vanzare=1013` (`cod=1140509`, an=2025, luna=8),
|
||||
`VANZARI.ID_FACT=8008013`, prezent in `ANAF_EFACTURA`.
|
||||
- **Caz B — NEtrimis** (regresie): descoperit prin proprietate (`DescoperaCazTest`,
|
||||
`FACTURA_ARTICOLE`), `cod=1140895` (id_vanzare=1050, documentul deja folosit de restul suitei).
|
||||
|
||||
Acopera: `EsteInEFactura` direct (cu `0` si cu `8008013`); `lArticoleReadOnly` corect pe ambele
|
||||
cazuri; **`PAGE3` NU e suprimata** (`PageCount=3`, `RecordSource` neschimbat, `tvd` cu linii);
|
||||
`cmdAdaugaArticol`/`cmdStergeArticol.Enabled`; vizibilitatea etichetei; garda pe
|
||||
`cmdStergeArticol.Click()` (apelat direct, fara dialog modal — confirma ca linia nu se modifica
|
||||
pe documentul din eFactura si ca se modifica normal pe cel obisnuit); `Grid1.ReadOnly` neatins
|
||||
(nota ramane editabila). Nu scrie in Oracle.
|
||||
|
||||
**Ce nu acopera** (documentat explicit in fisier, nu ascuns): editabilitatea per-celula
|
||||
(`.When()` pe coloanele gridului) — coloanele **nu se materializeaza sub `-A -T`**
|
||||
(`ColumnCount=0`, eroare 1925 „Unknown member" la accesul pe nume), artefact cunoscut si
|
||||
documentat (`grid-coloane-nu-se-materializeaza-headless`). Mutat in suita UI de mai jos.
|
||||
|
||||
### `COMUN\utile\Teste\editare_factura\test_ui_efactura_readonly.prg` (UI vizibil) — **14 PASS / 0 FAIL**
|
||||
|
||||
Rulat prin harnessul corect (`vfp_ui_harness.ps1 -TestPrg ... -Steps @(...) -SyncDir ...`), nu
|
||||
prin lansare directa — o incercare initiala prin `Start-Process` direct a dat tot `ColumnCount=0`;
|
||||
cauza reala **nu era lansarea**, ci lipsa apelului `IncarcaArticoleFactura(1013,
|
||||
'crsArticoleFactura')` **inainte** de `Createobject` — gridul se leaga o singura data, la
|
||||
construire (in `Load()`), pe cursorul `tvd` existent atunci; fara precarcare, `Load()` creeaza
|
||||
`tvd` GOL, iar fallback-ul din `Show()` il **recreeaza prin SQL** dupa constructie — cursor diferit
|
||||
de cel pe care s-a legat gridul, deci `ColumnCount` ramane 0. Corectat dupa tiparul deja folosit de
|
||||
`test_ui_s5_grid_pret_achizitie.prg`.
|
||||
|
||||
Acopera, pe acelasi document real (`id_vanzare=1013`, in eFactura): gridul are 15 coloane
|
||||
(neregresat); `.When()` pe toate cele patru controale (`cCantitateArt`, `cPretArt`,
|
||||
`cPretAchizitieArt`, `cPretCuTvaArt._checkbox1`) intorc `.F.` — **needitabile pe orice linie
|
||||
normala, nu doar pe liniile din set**; caz sintetic de control (`lArticoleReadOnly` comutat manual
|
||||
pe `.F.` pe acelasi document/aceleasi obiecte) — cele trei celule redevin editabile, deci gating-ul
|
||||
e demonstrat pe flag, nu pe o coincidenta a documentului ales. Captura:
|
||||
`screenshots_efactura\step_0_grid_efactura_readonly.png`. Nu scrie in Oracle.
|
||||
|
||||
## Ce NU s-a putut testa, si de ce
|
||||
|
||||
- **Dialogul de adaugare articol** (`frm_articol_factura`, `Show(1)` modal): garda de pe
|
||||
`cmdAdaugaArticol.Click()` (Enabled=.F. + guard in cod) nu s-a putut exercita prin click real
|
||||
headless — consecvent cu limitarea deja documentata pe restul suitei S4/S5 (dialog modal). Doar
|
||||
`Enabled=.F.` verificat direct.
|
||||
- **Ramura moarta preexistenta, gasita dar NEATINSA** (nu in scope): linia
|
||||
`IF !Used('tvd') OR !This.lAreArticoleVanzari OR Thisform.lArticoleReadOnly` din
|
||||
`cmdAdaugaArticol.Click` (`:16497`) foloseste `This.lAreArticoleVanzari` — `This` acolo e
|
||||
butonul, nu formularul, deci proprietatea nu exista pe el. Ramane neexecutata in practica pentru
|
||||
ca `!Used('tvd')` e `.F.` de fiecare data cand butonul chiar e vizibil (short-circuit VFP), deci
|
||||
runtime-ul nu ajunge niciodata sa evalueze operandul gresit. Preexistenta editarii mele
|
||||
(adaugarea mea e doar `OR Thisform.lArticoleReadOnly`, corect scris cu `Thisform`), nu se
|
||||
repara aici — in afara scope-ului deciziei 42.
|
||||
- **Verificarea vizuala pe ecran de Marius**: eticheta discreta, pozitionarea ei fata de butoane,
|
||||
culoarea, comportamentul real la click de mouse pe grid.
|
||||
|
||||
## Stare finala verificata
|
||||
|
||||
- **Write-back facut si dovedit** pentru `omodificari.vc2` (reconversie + diff octet cu octet,
|
||||
identic). `ofacturare_editare.prg` e `.prg` — sursa directa, fara pas de write-back.
|
||||
- Cens de octeti pe `omodificari.vc2`: **`2 aa / 2 e3 / 2 fe`, zero `EF BF BD`** — identic cu
|
||||
baseline, verificat dupa ultima editare.
|
||||
- Zero procese `vfp9.exe` ramase (verificat cu `tasklist`) dupa toate rularile mele. Procesul
|
||||
`vfp9.exe` al agentului `s8-creare-variante` (fereastra „S8 - creare documente") a ramas
|
||||
intact, neatins.
|
||||
- Zero scrieri in Oracle in toata sesiunea — toate interogarile (`EsteInEFactura`,
|
||||
`IncarcaCursoareModificareNota`, `IncarcaVanzareNota`, `IncarcaArticoleFactura`) sunt `SELECT`.
|
||||
- Zero commit (git/svn).
|
||||
- Diff consolidat: diff aplicat (sters) (ambele fisiere).
|
||||
- Fisiere noi, necomise: `COMUN\utile\Teste\editare_factura\test_efactura_readonly.prg` (+ log),
|
||||
`COMUN\utile\Teste\editare_factura\test_ui_efactura_readonly.prg` (+ log + screenshot in
|
||||
`screenshots_efactura\`).
|
||||
|
||||
## Interzis — respectat
|
||||
|
||||
`ofacturare.vc2`, `ofacturare_comun.vc2`, `comun.vc2`: neatinse (verificat — niciun `Edit`/`Write`
|
||||
pe ele in aceasta lucrare). `actualizeaza_vanzari`, `PACK_CONTAFIN`, `PACK_FACTURARE`: neatinse.
|
||||
Niciun `INSERT`/`UPDATE` pe `id_vanzare` `1049`/`1050`/`1048`.
|
||||
188
docs/cercetare/rec_datoria6_baza_regresie.md
Normal file
188
docs/cercetare/rec_datoria6_baza_regresie.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# Datoria 6 — ce s-a intamplat cu baza de regresie #6/S4 si cum a fost re-ancorata
|
||||
|
||||
09.08.2026. Schema `MARIUSM_AUTO@ROA_CENTRAL`. **Strict citiri pe Oracle** — nicio scriere, niciun
|
||||
commit.
|
||||
|
||||
## 1. Ce s-a intamplat cu documentul (dovada pe randuri)
|
||||
|
||||
Ipoteza de plecare **se confirma**: documentul nu s-a pierdut, i s-a **realocat `cod`-ul**.
|
||||
`ID_VANZARE = 1050` exista, e activ si nu si-a schimbat niciun total — doar `COD` a trecut de la
|
||||
**1140888** la **1140895**.
|
||||
|
||||
```
|
||||
ID_VANZARE COD STERS TIP NUMAR_ACT SERIE DATA_ACT ID_FACT TOTAL_CU_TVA
|
||||
1050 1140895 0 1 547 SSS 07.08.2026 8009660 1924.59
|
||||
1047 1140885 0 -12 544 SSS 07.08.2026 8009657 747.79
|
||||
1048 1140894 0 1 545 SSS 07.08.2026 8009658 302.51
|
||||
1049 1140887 0 1 546 SSS 07.08.2026 8009659 573.81
|
||||
```
|
||||
|
||||
Pe intervalul 1140880-1140900 exista **doar** aceste 4 randuri; `max(cod)` in `VANZARI` e
|
||||
**1140895**, `max(id_vanzare)` e **1050**. Nu exista niciun rand pe `cod = 1140888`.
|
||||
|
||||
Nota contabila arata acelasi lucru — acelasi antet, acelasi total, doar `STERS` si `COD` diferite:
|
||||
|
||||
```
|
||||
COD AN LUNA STERS N NRACT SERIE DATAACT ID_FACT SUMA_TOT
|
||||
1140888 2026 8 1 24 547 SSS 07.08.2026 8009660 4836.67
|
||||
1140895 2026 8 0 24 547 SSS 07.08.2026 8009660 4836.67
|
||||
```
|
||||
|
||||
24 de randuri vechi marcate `STERS=1` pe `cod` vechi, 24 de randuri noi active pe `cod` nou, cu
|
||||
`nract`/`serie_act`/`dataact`/`id_fact` identice si aceeasi suma. Este exact semnatura lui
|
||||
`finalizeaza_modificare_nota` + `pack_facturare.actualizeaza_vanzari(cod_vechi, cod_nou)`.
|
||||
`VANZARI_DETALII` pe `id_vanzare = 1050` are in continuare **4 linii active** — neatins, corect
|
||||
(scrierea in `VANZARI_DETALII` e S5, inca neimplementata).
|
||||
|
||||
**Cand**, pe secunda (`ACT.DATAORAS` = marcarea ca sters, `ACT.DATAORA` = crearea randului):
|
||||
|
||||
| Actiune | Moment |
|
||||
|---|---|
|
||||
| `cod=1140893` marcat sters, `cod=1140894` creat (`id_vanzare=1048`) | 08.08.2026 09:16:29 / 09:16:30 |
|
||||
| `cod=1140888` marcat sters, `cod=1140895` creat (`id_vanzare=1050`) | 08.08.2026 14:05:15 / 14:05:16 |
|
||||
|
||||
Deci **nu** testul de write-back aprobat a mutat documentul de regresie: acela a lucrat pe
|
||||
`id_vanzare = 1048` dimineata la 09:16 (`1140886 -> 1140893 -> 1140894`, consemnat in `progres.md`).
|
||||
Mutarea lui `1050` e o **a doua salvare, la 14:05**, pe un alt document — cel folosit ca ancora de
|
||||
regresie. Nu exista in `ACT` niciun `cod` intermediar intre 1140888 si 1140895 (1140889-1140892 n-au
|
||||
randuri), deci a fost o singura realocare.
|
||||
|
||||
**Nimic de recreat.** Datele nu s-au pierdut; ancorarea suitelor era gresita. Prin urmare **nu se
|
||||
cere nicio decizie de INSERT/UPDATE** din partea lui Marius pe partea de date.
|
||||
|
||||
Ancorele celorlalte cazuri sunt neatinse: `id_vanzare` 1047 (`cod=1140885`), 506 (`cod=1137874`),
|
||||
882 (`cod=1139934`) sunt toate active pe acelasi `cod` ca inainte — se realoca doar documentele care
|
||||
chiar se salveaza, adica cele din luna curenta, singurele care trec de garzile din
|
||||
`do_editare_factura`.
|
||||
|
||||
## 2. Cifrele masurate azi, inainte de modificare
|
||||
|
||||
Cifra din `progres.md` (`7 PASS / 3 FAIL`) era **veche**: numara doar cele 10 asertii de la runda 2,
|
||||
inainte ca blocul 3A sa adauge alte 5. Masurat azi, pe starea de pe disc:
|
||||
|
||||
| Suita | Inainte | Dupa |
|
||||
|---|---|---|
|
||||
| `test_page3_articole.prg` | **8 PASS / 7 FAIL** (15 verificari) | **13 PASS / 2 FAIL** |
|
||||
| `test_incarca_vanzare_din_nota.prg` | **4 PASS / 1 FAIL** | **5 PASS / 5** |
|
||||
|
||||
Ambele rulari (inainte si dupa): `exit code 0`, **0 dialoguri native**, sub
|
||||
`COMUN\utile\Teste\watchdog_vfp.ps1 -AutoDismiss` (care sterge `.fxp`-ul inainte de fiecare
|
||||
lansare). `loForm.ClassLibrary` e asigurat de blocul existent
|
||||
`RELEASE CLASSLIB omodificari` + `SET CLASSLIB TO D:\ROA\ROAFACTURARE\COMUN\clase\omodificari.vcx`,
|
||||
neatins de modificarea de fata.
|
||||
|
||||
Motivul concret al esecurilor: `IncarcaCursoareModificareNota` filtreaza `STERS = 0`, iar toate cele
|
||||
24 de randuri `ACT` de pe `cod=1140888` sunt `STERS=1` — deci `tact` venea **cu 0 randuri**, iar
|
||||
suita nici nu ajungea sa instantieze formularul (`PageCount = -1` in log).
|
||||
|
||||
## 3. Ce s-a schimbat in suite si de ce
|
||||
|
||||
Principiul aplicat e **varianta 1 din brief**: suitele isi descopera singure documentul de test, dupa
|
||||
**proprietatea ceruta de asertie**, nu dupa identitatea lui. Ancorarea pe `cod` era condamnata prin
|
||||
constructie (se realoca la fiecare salvare); ancorarea pe `id_vanzare` ar fi rezistat, dar tot cere
|
||||
un numar scris de mana intr-un fisier de test.
|
||||
|
||||
### Fisier nou: `COMUN\utile\Teste\editare_factura\descopera_caz_test.prg`
|
||||
|
||||
`DescoperaCazTest(<nume caz>, <alias>)` lasa in alias un rand cu `cod, an, luna, id_vanzare, tip,
|
||||
nlin` si intoarce `.T.`/`.F.` Sase cazuri, fiecare o proprietate:
|
||||
|
||||
| Caz | Proprietatea ceruta | Rezolvat azi la |
|
||||
|---|---|---|
|
||||
| `FACTURA_ARTICOLE` | `tip=1` activa, cu linii active, a carei nota are **primul** rand (`min(id_act)`) pe acelasi `(nract, serie_act, dataact)` ca vanzarea | `cod=1140895`, `id_vanzare=1050`, 4 linii |
|
||||
| `NEFACTURA_ARTICOLE` | la fel, dar `tip <> 1` (decizia 19 — detectia merge pe orice tip) | `cod=1140885`, `id_vanzare=1047`, `tip=-12` |
|
||||
| `FARA_RULAJE` | linii active + **zero** randuri in `vrul_tot` si `vrul_obinv_tot` | `cod=1140885`, `id_vanzare=1047` |
|
||||
| `PRIM_RAND_ORB` | nota al carei **prim** rand NU duce la vanzare, dar un triplet ulterior da | `cod=1140401`, `id_vanzare=1005` |
|
||||
| `COLIZIUNE_COD` | idem + `cod` cu 2+ randuri active in `VANZARI` + un triplet din nota fara corespondent | `cod=1139934`, `id_vanzare=882`, `nract` fara corespondent = 13 |
|
||||
| `NOTA_FARA_VANZARI` | nota activa fara rand in `VANZARI` pe **niciun** triplet | `cod=1140883`, an 2026, luna 7 |
|
||||
|
||||
Doua lucruri contau la proiectare:
|
||||
|
||||
- **Independenta fata de codul testat.** Interogarile merg pe `VACT_TOT` / `VRUL_TOT` /
|
||||
`VRUL_OBINV_TOT` / `VANZARI` / `VANZARI_DETALII` cu join direct, adica pe **alt drum** decat
|
||||
`IncarcaVanzareNota` / `IncarcaVanzareDinNota` / `IncarcaArticoleFactura`. Valoarea asteptata
|
||||
(`id_vanzare`, `tip`, numarul de linii) nu vine de la functia verificata, deci asertia nu devine
|
||||
tautologica.
|
||||
- **Ordonare determinista** (`order by v.id_vanzare desc`, respectiv `an/luna/cod desc`), ca doua
|
||||
rulari succesive pe aceleasi date sa aleaga acelasi document. Cazul ales e scris in log la
|
||||
inceputul fiecarei rulari, ca sa se vada pe ce document s-a masurat.
|
||||
- **Nicio potrivire = FAIL explicit**, nu test sarit: `caz_negasit` scrie in log
|
||||
`niciun document din schema nu satisface conditia cazului` + `FAIL`.
|
||||
|
||||
Interogarile au fost validate intai direct in `sqlplus` (fiecare intoarce documentul asteptat), abia
|
||||
apoi puse in cod.
|
||||
|
||||
### `test_page3_articole.prg`
|
||||
|
||||
Toate cele sase documente hardcodate (`1140888`, `1140885`, `1125486`, `1139934`, `1137874`) au fost
|
||||
inlocuite cu cazul descoperit corespunzator. Structura asertiilor e neschimbata; procedurile de
|
||||
verificare si-au pastrat corpul, doar au primit prin parametru ce inainte era scris in ele:
|
||||
|
||||
- `verifica_coliziune_cod` primea zero parametri si continea `1139934 / 375 / 'SSS' / 31.12.2021` si
|
||||
`nract=13`; acum primeste `cod`, tripletul care **trebuie** gasit, `id_vanzare` asteptat si
|
||||
**tripletul complet** care **nu trebuie** gasit. Descoperirea intoarce cele trei coloane ale
|
||||
randului negativ de pe acelasi rand (`keep (dense_rank first order by nract)`), ca sa nu se combine
|
||||
`nract`-ul unui rand cu `serie_act`-ul altuia — altfel asertia negativa ar fi trecut din alt motiv
|
||||
decat cel testat.
|
||||
- **O asertie s-a intarit, niciuna nu s-a slabit.** Cazul B (`tip <> 1`) trecea inainte cu
|
||||
`tnLiniiAsteptate = -1`, adica verificarea numarului de linii era dezactivata; acum primeste
|
||||
numarul real din `VANZARI_DETALII` (2) si il verifica.
|
||||
|
||||
### `test_incarca_vanzare_din_nota.prg`
|
||||
|
||||
Aceleasi patru cazuri, plus cazul EOF, trecute pe descoperire. Nicio schimbare de asertie.
|
||||
|
||||
## 4. Ce a ramas neacoperit
|
||||
|
||||
**Doua asertii din blocul 3A raman FAIL, si NU din cauza datelor** — sunt artefactul de mediu deja
|
||||
consemnat ca datoria 7 in `progres.md`:
|
||||
|
||||
```
|
||||
EROARE 1925 [VERIFICA_EDITARE_GRID:353] Unknown member COLUMN5.
|
||||
structura grid/cursor (ColumnCount=14, lmodificat L, valoare N) = FAIL
|
||||
ReadOnly (cantitate/pret/pret_cu_tva editabile, checkbox pe pret_cu_tva, restul readonly) = FAIL
|
||||
stare initiala (lmodificat=.F., valoare calculata corect) = PASS
|
||||
dupa editare cantitate (lmodificat=.T., valoare recalculata) = PASS
|
||||
```
|
||||
|
||||
Sub `vfp9.exe -A -T` grid-ul nu se materializeaza: `ColumnCount` raporteaza `0` si `ColumnN` nu
|
||||
exista ca membru, oricat de complet ar fi definita clasa. Repartitia 2 FAIL (structura, `ReadOnly`)
|
||||
/ 2 PASS (cursor, calcul) e **exact** cea prezisa in `progres.md`, datoria 7 — deci suita e acum
|
||||
inapoi la starea ei dinaintea degradarii bazei, nu mai bine si nu mai rau.
|
||||
|
||||
Cele doua asertii **nu au fost atinse, slabite sau sterse**. Ele sunt oricum acoperite corect pe
|
||||
ecran de `COMUN\utile\Teste\editare_factura\test_ui_fix_editabil_subtotal.prg` sub
|
||||
`vfp_ui_harness.ps1` (11/11 PASS, `ColumnCount=14`, consemnat in `progres.md`). Daca se doreste
|
||||
curatarea zgomotului, varianta corecta e cea deja propusa la datoria 7 — rescrierea lor ca
|
||||
verificare **statica** pe memo-ul `Properties` din `.vcx` — dar asta e alta lucrare, nu re-ancorare.
|
||||
|
||||
Altele:
|
||||
|
||||
- Nu s-a verificat comportamentul suitelor pe alta schema decat `MARIUSM_AUTO`. Descoperirea e
|
||||
scrisa sa mearga pe orice schema, dar nu a fost probata pe `ROMFAST` sau `VENDING`.
|
||||
- Cazurile `PRIM_RAND_ORB` si `NOTA_FARA_VANZARI` se rezolva azi la alte documente decat inainte
|
||||
(`1140401` in loc de `1137874`, `1140883` in loc de `1125486`) — proprietatea testata e insa
|
||||
aceeasi, iar ambele trec. Vechile documente raman valide, doar ca nu mai sunt primele in ordinea
|
||||
determinista.
|
||||
- Nu s-a atins nimic din `omodificari.vc2`, `ofacturare_comun.vc2`, `ofacturare_editare.prg` sau
|
||||
binarele lor. `git status` in `COMUN` confirma: singurele fisiere de test schimbate sunt cele doua
|
||||
suite plus fisierul nou.
|
||||
|
||||
## 5. Fisiere
|
||||
|
||||
| Fisier | Stare |
|
||||
|---|---|
|
||||
| `COMUN\utile\Teste\editare_factura\descopera_caz_test.prg` | **nou** |
|
||||
| `COMUN\utile\Teste\editare_factura\test_page3_articole.prg` | modificat |
|
||||
| `COMUN\utile\Teste\editare_factura\test_incarca_vanzare_din_nota.prg` | modificat |
|
||||
| diff aplicat (sters) | diff-ul celor trei |
|
||||
|
||||
Comenzile de rulare:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File D:\ROA\ROAFACTURARE\COMUN\utile\Teste\watchdog_vfp.ps1 -Script 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_page3_articole.prg' -AutoDismiss -TimeoutSec 300
|
||||
powershell -ExecutionPolicy Bypass -File D:\ROA\ROAFACTURARE\COMUN\utile\Teste\watchdog_vfp.ps1 -Script 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_incarca_vanzare_din_nota.prg' -AutoDismiss -TimeoutSec 240
|
||||
```
|
||||
|
||||
Logurile: `..._log.txt` langa fiecare suita; rezumatul watchdog-ului in
|
||||
`COMUN\utile\Teste\editare_factura\watchdog_out\`.
|
||||
312
docs/cercetare/rec_dec42_proiectare.md
Normal file
312
docs/cercetare/rec_dec42_proiectare.md
Normal file
@@ -0,0 +1,312 @@
|
||||
# Proiectare — Decizia 42: articole factura vizibile, needitabile dupa trimiterea in eFactura
|
||||
|
||||
Scris 10.08.2026. Agent read-only, doar cercetare/proiectare — nicio modificare de cod facuta
|
||||
de acest agent.
|
||||
|
||||
## ATENTIE — implementarea era deja IN LUCRU, de catre alt agent; bugul de mai jos e DEJA CORECTAT
|
||||
|
||||
Inainte de a ajunge la proiectare: la momentul cercetarii, `COMUN\clase\omodificari.vc2` avea deja
|
||||
**modificari necomise in working tree** (`git status` in `COMUN`: `M clase/omodificari.vc2`) care
|
||||
implementeaza aproape exact Decizia 42 corectata (vizibil, doar needitabil). Agentul `d42-efactura`,
|
||||
activ in aceeasi sesiune, lucra chiar atunci pe exact acest subiect.
|
||||
|
||||
**Acest raport nu descrie deci o proiectare de la zero, ci: (1) verificarea contractelor si (2) un
|
||||
review al diff-ului in lucru, cu un bug real gasit si dovedit pe schema Oracle — vezi §1 si §8.**
|
||||
|
||||
**UPDATE, dupa trimiterea raportului**: `d42-efactura` a gasit acelasi bug independent, in timpul
|
||||
propriei lucrari (nu din briefingul primit de la team-lead), l-a corectat si scris in binar
|
||||
**inainte** sa primeasca mesajul meu. **Verificat direct pe disc de acest agent** (nu doar preluat
|
||||
din raportarea lui `d42-efactura`): `git diff` pe `COMUN\clase\omodificari.vc2` arata
|
||||
`This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact,0))`, iar `git diff` pe
|
||||
`COMUN\programe\ofacturare_editare.prg` arata `v.id_fact` adaugat in SELECT-ul din
|
||||
`IncarcaVanzareNota` si `id_fact I NULL` adaugat in schema `CREATE CURSOR tvanz` din
|
||||
`CreeazaCursorTvanzGol` — exact varianta B recomandata la §8. **Bugul e inchis, nu mai necesita
|
||||
actiune.**
|
||||
|
||||
Legat de linia necomisa gasita atunci in `ROAGEST\Programe\roagest.prg`
|
||||
(`SET PROCEDURE TO ofacturare_editare.prg ADDITIVE`, "Not Committed Yet" la 10.08.2026 11:28):
|
||||
`d42-efactura` confirma ca nu e a lui — a atins doar `omodificari.vc2` si `ofacturare_editare.prg`
|
||||
(plus fisiere de test noi). Ramane deci dintr-un alt bloc de lucru (posibil S9), de verificat separat
|
||||
de team-lead — nu descrie starea lui `d42-efactura`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Cum se afla ca documentul e in eFactura
|
||||
|
||||
**Functie**: `FUNCTION EsteInEFactura`, globala (nu metoda de clasa), definita in
|
||||
`COMUN\programe\ofacturare_editare.prg:16-25`:
|
||||
|
||||
```
|
||||
*!* parametru: id_fact
|
||||
*!* verifica daca factura cu id_fact dat a fost deja trimisa in eFactura (anaf_efactura)
|
||||
FUNCTION EsteInEFactura
|
||||
LPARAMETERS tnIdFact
|
||||
LOCAL lcSql, lnEFactura, llSucces
|
||||
lnEFactura = 0
|
||||
lcSql = [select count(*) as lneFactura from anaf_efactura where id_fact = ] + Alltrim(Str(Nvl(m.tnIdFact,0)))
|
||||
llSucces = goExecutor.oSelecteaza2Value(m.lcSql, @lnEFactura)
|
||||
RETURN (Nvl(m.lnEFactura,0) > 0)
|
||||
ENDFUNC
|
||||
```
|
||||
|
||||
**Contract, deschis din `RETURN`**: primeste `tnIdFact` — **`ID_FACT`, nu `ID_VANZARE`** — si intoarce
|
||||
`.T./.F.` (numar de randuri in `ANAF_EFACTURA` cu acel `id_fact` > 0). Foloseste `goExecutor`
|
||||
(disponibil global, aceeasi conventie ca restul aplicatiei).
|
||||
|
||||
**Disponibilitate cross-project — VERIFICAT, nu presupus**: `ofacturare_editare.prg` e inregistrat
|
||||
prin `SET PROCEDURE ... ADDITIVE` in toate cele trei aplicatii:
|
||||
|
||||
| App | Fisier | Linie | Stare git |
|
||||
|---|---|---|---|
|
||||
| ROAFACTURARE | `Programe\roafacturare.prg` | 214 | comis demult |
|
||||
| ROACONT | `Programe\roacont.prg` | 212 | comis, `3af0089` (08.08.2026) — mesaj: *"Fara el, pagina de articole ale facturii nu apare in registrul jurnal > modificare"* |
|
||||
| ROAGEST | `Programe\roagest.prg` | 260 | **necomis**, adaugat azi 10.08.2026 (vezi avertismentul de mai sus) |
|
||||
|
||||
Deci `EsteInEFactura` **este** apelabila din contextul `omodificari.vc2`, inclusiv din ROACONT si
|
||||
(dupa commit-ul in curs) ROAGEST. Comentariul din `omodificari.vc2:14786-14787` ("apare doar cand
|
||||
ofacturare_editare.prg e incarcat (doar ROAFACTURARE - ROACONT/ROAGEST nu-l inregistreaza)") e
|
||||
**invechit** — scris inainte de commit-ul `3af0089`, care a inversat exact aceasta premisa pentru
|
||||
ROACONT. Nu se corecteaza comentariul in acest raport (read-only), dar oricine implementeaza ar
|
||||
trebui sa-l actualizeze cand atinge zona.
|
||||
|
||||
**BUG GASIT in diff-ul in lucru — `id_fact` confundat cu `id_vanzare`.** Apelul din
|
||||
`omodificari.vc2:14798` (diff necomis) e:
|
||||
|
||||
```
|
||||
This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare)
|
||||
```
|
||||
|
||||
dar `This.nIdVanzare` e populat cu `tvanz.id_vanzare` (linia 14796), adica `VANZARI.ID_VANZARE` —
|
||||
**alt camp** decat `VANZARI.ID_FACT`, pe care `EsteInEFactura` il asteapta. Dovada, in trei straturi:
|
||||
|
||||
1. **Cod**: precedentul functional existent, `ofacturare_comun.vc2:3739` (`lnIdFact = id_fact`) si
|
||||
`:3764` (`EsteInEFactura(lnIdFact)`), citeste `id_fact` dintr-un camp separat de `id_vanzare`
|
||||
(`:3734`, `lnIdVanzare = id_vanzare`, aceeasi cursor `crsfacturi`, doua LOCAL-uri distincte).
|
||||
Acelasi tipar in `anaf_efactura.prg:3123-3124`: `pnIdFact = id_fact` si `lnIdVanzare = id_vanzare`
|
||||
citite **pe acelasi rand** din `crsFacturiEmise` — daca ar fi acelasi numar, codul n-ar avea
|
||||
nevoie de doua variabile.
|
||||
2. **Schema Oracle** (verificat read-only, `user_tab_columns`, `MARIUSM_AUTO`): `VANZARI` are **ambele**
|
||||
coloane, `ID_FACT` si `ID_VANZARE`, distincte; `ANAF_EFACTURA` are doar `ID_FACT`.
|
||||
3. **Date reale** (verificat read-only, join `anaf_efactura.id_fact = vanzari.id_fact`): pentru
|
||||
documentele deja trimise in eFactura, cele doua valori difera constant —
|
||||
|
||||
| id_fact | id_vanzare | cod | numar_act | data_act |
|
||||
|---|---|---|---|---|
|
||||
| 8008013 | 1013 | 1140509 | 510 | 31-AUG-25 |
|
||||
| 8007922 | 1007 | 1140439 | 503 | 03-JAN-25 |
|
||||
| 8007836 | 993 | 1140380 | 490 | 15-AUG-24 |
|
||||
| 8007810 | 991 | 1140369 | 488 | 11-JUL-24 |
|
||||
|
||||
Cu bug-ul curent, `EsteInEFactura(1013)` cauta `id_fact = 1013` in `ANAF_EFACTURA` — care nu exista
|
||||
cu acel numar — deci **intoarce mereu `.F.`**, chiar si pentru documente reale trimise in eFactura.
|
||||
Garda ar fi complet inoperanta pe date reale. Corectia: §8.
|
||||
|
||||
Niciun document dintre cele patru de mai sus nu e din luna curenta (limitare de date deja cunoscuta
|
||||
din bloc — garda de editare cere luna curenta), deci un test headless pe un caz real din productie
|
||||
de date nu se poate face acum; testul trebuie sa foloseasca un cursor `tact`/`tvanz` mock cu
|
||||
`id_fact` setat manual (acelasi tipar folosit deja pentru `id_vanzare_set` in S5).
|
||||
|
||||
## 2. Unde se aseaza conditia in omodificari.vc2
|
||||
|
||||
Locul folosit deja de diff-ul in lucru e corect si e cel recomandat: `frm_modific2024.Show()`
|
||||
(`omodificari.vc2:14748-14837` in varianta comisa `1c42ae0`; extins la 14748-14856 in diff-ul
|
||||
necomis), imediat dupa blocul care rezolva `This.nIdVanzare`/`This.nTipVanzare` prin
|
||||
`IncarcaVanzareDinNota('tact')` si inainte de `This.pgfArticole.PageCount = 3`. E punctul unde
|
||||
formularul stie deja daca documentul curent are rand in `VANZARI` (`This.lAreArticoleVanzari`) —
|
||||
conditia eFactura se agata direct pe acest rezultat, fara sa duplice logica de potrivire
|
||||
cod/nract/serie_act/dataact -> id_vanzare.
|
||||
|
||||
Proprietatea noua, `lArticoleReadOnly` (declarata la nivelul clasei, langa `lAreArticoleVanzari`,
|
||||
`omodificari.vc2:6827` si `6867` in diff), e citita apoi din `.When`-urile coloanelor editabile ale
|
||||
gridului `grdArticoleFactura` (mecanismul de editabilitate per rand din S5, §3-4) — deci conditia
|
||||
de formular **compune** cu mecanismul existent, nu-l inlocuieste.
|
||||
|
||||
## 3. Controale care se dezactiveaza — confirmate pe cod
|
||||
|
||||
| Control | Path | Linii (comis) | Ce face diff-ul |
|
||||
|---|---|---|---|
|
||||
| Buton adaugare | `pgfArticole.PAGE3.cmdAdaugaArticol` | Click: `16488-16529` | `.Enabled = !This.lArticoleReadOnly` la afisare (`Show`); plus guard `OR Thisform.lArticoleReadOnly` in `Click` (aparare in adancime, cazul `Enabled` sarit programatic) |
|
||||
| Buton stergere | `pgfArticole.PAGE3.cmdStergeArticol` | Click: `16531-16540` | idem |
|
||||
| Cantitate | `grdArticoleFactura.cCantitateArt.Text1.When` | `16549-16554` | adauga `Thisform.lArticoleReadOnly OR` in fata conditiei existente `Nvl(tvd.id_vanzare_set,0)<>0` |
|
||||
| Pret achizitie | `grdArticoleFactura.cPretAchizitieArt.Text1.When` | `16556-16561` | idem |
|
||||
| Pret | `grdArticoleFactura.cPretArt.Text1.When` | `16570-16575` | idem |
|
||||
| Pret cu TVA (checkbox) | `grdArticoleFactura.cPretCuTvaArt._checkbox1.When` | `16587-16589` | `RETURN !Thisform.lArticoleReadOnly AND ...` |
|
||||
| Discount | `pgfArticole.PAGE3.txtDiscountArt` | — | **neatins in diff** — vezi nota de mai jos |
|
||||
|
||||
**Gol observat**: `txtDiscountArt` (discountul pe factura, cu `ControlSource = tvanz.discount`) nu
|
||||
are `.When` si nu apare in diff. Daca discountul e considerat parte din "articolele facturii" in
|
||||
sensul Deciziei 42 (afecteaza totalul liniilor), ramane editabil dupa trimiterea in eFactura — de
|
||||
clarificat cu Marius sau de inclus explicit. Nu era in lista `cmdAdaugaArticol`/`cmdStergeArticol`
|
||||
ceruta explicit in brief, deci il semnalez ca gol, nu-l tratez ca bug.
|
||||
|
||||
## 4. Tipar read-only pe grid, folosit deja in proiect
|
||||
|
||||
Nu se inventeaza un tipar nou. Proiectul (din S5) foloseste deja exact acest idiom pentru randurile
|
||||
needitabile (liniile din seturi, `id_vanzare_set <> 0`): fiecare coloana editabila a gridului are
|
||||
un handler `.When` care intoarce `.F.` cand conditia de blocare e adevarata — VFP nu lasa controlul
|
||||
sa intre in editare daca `.When` intoarce `.F.`. Diff-ul in lucru extinde exact aceste `.When`-uri
|
||||
existente, adaugand `Thisform.lArticoleReadOnly OR` in fata conditiei deja acolo — e continuarea
|
||||
directa a mecanismului S5, nu un tipar paralel. Confirma cererea din brief: "foloseste tiparul deja
|
||||
folosit in proiect, nu inventa unul nou" — respectat.
|
||||
|
||||
## 5. Feedback vizual pentru utilizator
|
||||
|
||||
Diff-ul adauga o eticheta noua, `pgfArticole.PAGE3.lblArticoleReadOnly`
|
||||
(`omodificari.vc2:12857-12871` in diff), cu `Caption = "Articole needitabile - factura trimisa in
|
||||
eFactura"`, `Visible` legat de `This.lArticoleReadOnly` in `Show()`. E minimul cerut de brief —
|
||||
niciun tipar de marcaj standard read-only nu exista deja in proiect (nu s-a gasit unul in cautarea
|
||||
pentru §4), deci eticheta simpla e alegerea rezonabila. Singura observatie: eticheta e pozitionata
|
||||
`Top = 4, Left = 360` — de verificat pe ecran (fara VFP IDE nu se poate confirma vizual) ca nu se
|
||||
suprapune cu alt control din PAGE3 la latimile de forma folosite azi.
|
||||
|
||||
## 6. Suprafata de regresie — de ce notele obisnuite din registrul jurnal nu sunt atinse
|
||||
|
||||
Blocul care calculeaza `lArticoleReadOnly` ruleaza **doar** in interiorul conditiei deja existente:
|
||||
|
||||
```
|
||||
IF "OFACTURARE_EDITARE" $ Upper(Set("Procedure")) AND Reccount('tact') > 0
|
||||
IncarcaVanzareDinNota('tact')
|
||||
IF Reccount('tvanz') = 1
|
||||
This.lAreArticoleVanzari = .T.
|
||||
...
|
||||
This.lArticoleReadOnly = EsteInEFactura(...)
|
||||
ENDIF
|
||||
ENDIF
|
||||
```
|
||||
|
||||
`IncarcaVanzareDinNota` cauta in `VANZARI` un rand cu tripletul (cod, nract, serie_act, dataact) al
|
||||
notei curente. Pentru o nota contabila obisnuita (nu factura de vanzare), acest rand nu exista,
|
||||
`Reccount('tvanz')` ramane `0`, deci **intreg blocul `IF Reccount('tvanz') = 1` e sarit** —
|
||||
`This.lAreArticoleVanzari` ramane `.F.` si `This.lArticoleReadOnly` ramane la valoarea implicita
|
||||
`.F.` (setata explicit chiar inainte de bloc, `:14791`). Consecinta directa: `This.pgfArticole.PageCount
|
||||
= 2` (fara PAGE3), deci `cmdAdaugaArticol`/`cmdStergeArticol`/gridul de articole **nici nu exista**
|
||||
pentru documentul respectiv — nu doar ca nu sunt dezactivate, ci nu sunt instantiate in fluxul de
|
||||
afisare. Riscul de regresie pe notele obisnuite din ROAGEST/ROACONT e deci **zero prin constructie**,
|
||||
mostenit din gating-ul `lAreArticoleVanzari` deja livrat si testat in S4/S5 — Decizia 42 doar adauga
|
||||
o conditie suplimentara **in interiorul** ramurii deja izolate pentru facturi de vanzare, nu schimba
|
||||
gating-ul insusi. Acesta e exact argumentul cerut in brief la punctul 6, si e verificabil citind
|
||||
codul, nu presupus.
|
||||
|
||||
`comun.vc2` (clasa `afisjurcom`, `do_modifica`, `2222-2572`) nu e atins de acest diff — ramane
|
||||
neschimbat, confirmand ca intreaga logica sta in `omodificari.vc2`, un singur punct de intretinere.
|
||||
|
||||
## 7. Teste minime propuse (headless, in stilul suitei existente)
|
||||
|
||||
Suita `COMUN\utile\Teste\editare_factura\` foloseste harness-ul `ui_harness.prg` + `asserteaza`, cu
|
||||
formular vizibil (`WindowType = 0`, `Show()`, `DOEVENTS FORCE`), fara input real (nici un
|
||||
`keybd_event`/`SendInput` — doar citire directa de proprietati si apel direct de metode). Propunere,
|
||||
dupa modelul `test_ui_sterge_linie.prg`:
|
||||
|
||||
**`test_d42_efactura_readonly.prg`** — cazul pozitiv, cu date mock (nu exista document real din
|
||||
luna curenta trimis in eFactura, cf. §1):
|
||||
1. Incarca `tact`/`tvd` pentru un document real din luna curenta cu `lAreArticoleVanzari = .T.`
|
||||
(ex. acelasi `id_vanzare = 1049` mentionat in handoff intermediar (sters), daca inca in luna
|
||||
curenta la momentul rularii).
|
||||
2. Dupa `IncarcaVanzareDinNota`, forteaza `tvanz.id_fact` (sau `tact.id_fact`, dupa care varianta se
|
||||
alege in §8) la o valoare stiuta ca exista in `ANAF_EFACTURA` (mock: `INSERT` intr-un cursor
|
||||
local, nu in Oracle) — sau, mai simplu, mock-uieste direct `EsteInEFactura` prin
|
||||
`SET PROCEDURE`/redefinire temporara, dupa tiparul deja folosit in proiect pentru izolarea de
|
||||
Oracle in teste UI.
|
||||
3. `assert`: `loForm.lArticoleReadOnly = .T.`
|
||||
4. `assert`: `loForm.pgfArticole.PAGE3.cmdAdaugaArticol.Enabled = .F.`
|
||||
5. `assert`: `loForm.pgfArticole.PAGE3.cmdStergeArticol.Enabled = .F.`
|
||||
6. `assert`: `loForm.pgfArticole.PAGE3.lblArticoleReadOnly.Visible = .T.`
|
||||
7. `assert`: grid-ul e in continuare **vizibil** si populat — `loForm.pgfArticole.PageCount = 3`,
|
||||
`Reccount('tvd') > 0` — confirma partea corectata a deciziei (nu se suprima pagina).
|
||||
8. `assert`: pozitionare pe `grdArticoleFactura`, `SetFocus` pe coloana `cCantitateArt` nu intra in
|
||||
editare — verificat prin apelul direct al handlerului `.When` (`loForm.pgfArticole.PAGE3.
|
||||
grdArticoleFactura.cCantitateArt.Text1.When()` trebuie sa intoarca `.F.`), nu prin tastare.
|
||||
|
||||
**`test_d42_efactura_editabil.prg`** — cazul negativ (control): acelasi document, dar
|
||||
`EsteInEFactura` mock-uit sa intoarca `.F.` -> toate assert-urile de mai sus inversate (`Enabled =
|
||||
.T.`, `.When()` nu intoarce `.F.` din cauza flagului — poate intoarce `.F.` din alt motiv, ex.
|
||||
`id_vanzare_set`, testat separat).
|
||||
|
||||
**`test_d42_nota_obisnuita_neatinsa.prg`** — cazul de regresie cerut la §6: incarca o nota
|
||||
contabila fara corespondent in `VANZARI` (orice test existent din `COMUN\utile\Teste\` care nu tine
|
||||
de facturare), verifica `loForm.pgfArticole.PageCount = 2` si ca `lArticoleReadOnly` ramane `.F.`
|
||||
fara sa fi fost nevoie sa se apeleze `EsteInEFactura` deloc (se poate confirma indirect, verificand
|
||||
ca nu s-a facut nicio interogare Oracle suplimentara, sau direct daca se mock-uieste functia cu un
|
||||
contor de apeluri).
|
||||
|
||||
Toate cele trei suite: fara scriere in Oracle, `QUIT` la final, log langa `.prg` cu acelasi nume +
|
||||
`_log.txt`, conform conventiei din handoff intermediar (sters).
|
||||
|
||||
## 8. Schita de diff — corectia necesara peste diff-ul in lucru
|
||||
|
||||
Doua variante, cu recomandare pentru B.
|
||||
|
||||
**Varianta A — minima**, refoloseste `id_fact` deja prezent pe cursorul `tact` (confirmat: `tact`
|
||||
vine din view-ul `vact_tot`, care are coloana `id_fact`, folosita deja in `ofacturare_comun.vc2:3776`
|
||||
ca `Locate For Nvl(id_fact, 0) = lnIdFact` pe acelasi cursor `actactan`/`tact`):
|
||||
|
||||
```diff
|
||||
--- a/COMUN/clase/omodificari.vc2
|
||||
+++ b/COMUN/clase/omodificari.vc2
|
||||
@@ frm_modific2024.Show
|
||||
This.nIdVanzare = tvanz.id_vanzare
|
||||
This.nTipVanzare = tvanz.tip
|
||||
- This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare)
|
||||
+ This.lArticoleReadOnly = EsteInEFactura(Nvl(tact.id_fact, 0))
|
||||
```
|
||||
|
||||
Risc al variantei A: presupune ca recordul curent din `tact` (la momentul `Show()`, imediat dupa
|
||||
incarcare, deci pe Top) are `id_fact` valid pentru documentul gasit — valabil in cazul normal, dar
|
||||
nu la fel de robust ca precedentul din `ofacturare_comun.vc2:3775-3783`, care cauta explicit randul
|
||||
cu `id_fact` potrivit inainte sa cada pe fallback (`Go Top`).
|
||||
|
||||
**Varianta B — recomandata**, aduce `id_fact` chiar pe `tvanz` (randul din `VANZARI` deja gasit prin
|
||||
tripletul cod/nract/serie_act/dataact), simetric cu `id_vanzare`:
|
||||
|
||||
```diff
|
||||
--- a/COMUN/programe/ofacturare_editare.prg
|
||||
+++ b/COMUN/programe/ofacturare_editare.prg
|
||||
@@ CreeazaCursorTvanzGol
|
||||
- CREATE CURSOR tvanz (id_vanzare I, tip I, discount N(12,2), total_fara_tva N(14,2), total_tva N(14,2), total_cu_tva N(14,2), curs N(10,4), multiplicator N(10,4), ;
|
||||
+ CREATE CURSOR tvanz (id_vanzare I, id_fact I NULL, tip I, discount N(12,2), total_fara_tva N(14,2), total_tva N(14,2), total_cu_tva N(14,2), curs N(10,4), multiplicator N(10,4), ;
|
||||
in_valuta I NULL, id_valuta I NULL, nume_val C(20) NULL)
|
||||
@@ IncarcaVanzareNota
|
||||
- lcSql = [select v.id_vanzare, v.tip, v.discount, v.total_fara_tva, v.total_tva, v.total_cu_tva, v.curs, v.multiplicator, ] + ;
|
||||
+ lcSql = [select v.id_vanzare, v.id_fact, v.tip, v.discount, v.total_fara_tva, v.total_tva, v.total_cu_tva, v.curs, v.multiplicator, ] + ;
|
||||
[v.in_valuta, v.id_valuta, nv.nume_val from vanzari v left join nom_valute nv on nv.id_valuta = v.id_valuta where v.cod = ] + Transform(m.tnCod) + ;
|
||||
|
||||
--- a/COMUN/clase/omodificari.vc2
|
||||
+++ b/COMUN/clase/omodificari.vc2
|
||||
@@ frm_modific2024.Show
|
||||
This.nIdVanzare = tvanz.id_vanzare
|
||||
This.nTipVanzare = tvanz.tip
|
||||
- This.lArticoleReadOnly = EsteInEFactura(This.nIdVanzare)
|
||||
+ This.lArticoleReadOnly = EsteInEFactura(Nvl(tvanz.id_fact, 0))
|
||||
```
|
||||
|
||||
Varianta B e mai robusta pentru ca foloseste `VANZARI.ID_FACT` direct — exact coloana pe care
|
||||
`ANAF_EFACTURA` o are ca cheie (verificat pe schema Oracle, §1) — fara sa depinda de pozitia
|
||||
curenta a cursorului `tact`. Costul: doua fisiere in loc de unul (`ofacturare_editare.prg` +
|
||||
`omodificari.vc2`), plus verificarea ca `select v.id_fact` nu produce eroare Oracle daca vreun rand
|
||||
vechi din `VANZARI` are `id_fact` `NULL` (coloana e deja `NULL`-abila judecand dupa restul cursorului,
|
||||
`in_valuta I NULL` etc., deci nu ar trebui sa fie o problema).
|
||||
|
||||
**Neschimbat, corect asa cum e**: restul diff-ului (`.When`-urile pe grid, `cmdAdaugaArticol`/
|
||||
`cmdStergeArticol.Enabled`, eticheta `lblArticoleReadOnly`) — vezi §2-5.
|
||||
|
||||
---
|
||||
|
||||
## Rezumat pentru implementare
|
||||
|
||||
1. Diff-ul lui `d42-efactura` (necomis la momentul acestui raport, in `COMUN\clase\omodificari.vc2`
|
||||
+ `COMUN\programe\ofacturare_editare.prg`) e **structural corect** — locul (§2), controalele
|
||||
(§3), tiparul de grid (§4) si feedback-ul (§5) respecta cerintele Deciziei 42 corectate.
|
||||
2. **Bug gasit, dovedit pe schema si date, si INCHIS**: `EsteInEFactura(This.nIdVanzare)` trebuia sa
|
||||
foloseasca `id_fact`, nu `id_vanzare` — `d42-efactura` l-a gasit independent si l-a corectat cu
|
||||
exact varianta B din §8 (`EsteInEFactura(Nvl(tvanz.id_fact,0))`), **verificat pe disc de acest
|
||||
agent** dupa corectie. Nu mai necesita nicio actiune.
|
||||
3. Gol de clarificat, nu bug: `txtDiscountArt` ramane editabil dupa eFactura — de decis cu Marius
|
||||
daca discountul intra sub "articole" (§3). Confirmat si de `d42-efactura` ca gol cunoscut, in
|
||||
afara scope-ului primit de la team-lead.
|
||||
4. Comentariul din `omodificari.vc2:14786-14787` ("ROACONT/ROAGEST nu-l inregistreaza") e invechit
|
||||
de la commit-ul `3af0089` — de actualizat cand se atinge zona (§1).
|
||||
5. Teste propuse in §7, aliniate stilistic cu suita existenta — de confirmat cu `d42-efactura` daca
|
||||
au fost deja scrise (mesajul lui mentioneaza "doua fisiere noi de test").
|
||||
6. `ROAGEST\Programe\roagest.prg` (linia necomisa `SET PROCEDURE TO ofacturare_editare.prg`) **nu**
|
||||
apartine lui `d42-efactura` — ramane dintr-un alt bloc de lucru, de identificat separat de
|
||||
team-lead.
|
||||
262
docs/cercetare/rec_editare_factura.md
Normal file
262
docs/cercetare/rec_editare_factura.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Cercetare: editare factura emisa (netrimisa inca in eFactura)
|
||||
|
||||
Sursa: cache text `.??2` (deja la zi, negenerate acum) + `.prg` din working copy
|
||||
`D:\ROA\ROAFACTURARE`. Cod Oracle (pachete `PACK_FACTURARE`, `PACK_CONTAFIN`, `PACK_DOCUMENTE`)
|
||||
**NU e in working copy** — doar apelurile RPC din VFP sunt vizibile; corpul PL/SQL trebuie cautat
|
||||
in schema Oracle, nu exista local.
|
||||
|
||||
## 1. Fluxul de EMITERE factura
|
||||
|
||||
**Punct de intrare (meniu -> procedura generica `factureaza`)**, `COMUN\programe\oproceduri_facturare.prg`:
|
||||
- `facturare_contracte` (:130-134) -> `factureaza(2/6/52)` — pe baza de **contract**
|
||||
- `facturare_comenzi` (:140) -> `factureaza(3)` — pe baza de **comanda**
|
||||
- `facturare_avize` (:145) -> `factureaza(4)` — pe baza de **aviz**
|
||||
- `emitere_aviz_clienti` / `_debitori` / `_custodie` / `_transfer` (:203-275) -> `factureaza(tip)` cu tip-uri diverse
|
||||
pentru avize (comanda/lista preturi/contract/lucrare/NIR/retur)
|
||||
- `copiere_factura` (:152) -> `factureaza(toFactura.Tip, toFactura)` — copiere/modificare-inainte-de-emitere
|
||||
(nu e "editare dupa emitere", e o factura noua pornita din datele alteia)
|
||||
|
||||
`factureaza` (`COMUN\programe\ofacturare.prg:90`) delegheaza imediat la **`factureaza2`** (acelasi fisier,
|
||||
`:170`+; bucla mare pana ~`:850`). Pasi, in ordine:
|
||||
|
||||
1. **Formular date antet**: `frm_date_factura` / `frm_date_aviz` / `frm_date_aviz_lucrare` (alegere dupa `tnTip`,
|
||||
`ofacturare.prg:220-226`) — culege client, data, delegat, ruta, incasare etc. in obiectul `poDate`.
|
||||
2. **Alocare numar document**: `poGeneratorNumere.verifica_numar(...)` / `.dezaloca_numar(...)` (`:250-260`) —
|
||||
numerotare seriala pe tip document, alocata/deblocata aici.
|
||||
3. **Populare cursor articole**, ramificat dupa `tnTip` (vezi punctul 6 mai jos) — cheama diverse
|
||||
`pack_facturare.cursor_*` (preturi/contract/comanda/avize/gestiune/lucrare/retur/aviz_nir) — `:266-308`.
|
||||
4. **Formular articole**: `frm_facturare_articole` / `frm_facturare_articole2` / `frm_avizare_lucrare`
|
||||
(`COMUN\clase\ofacturare.vc2`) — user editeaza cantitati/preturi/explicatii inainte de scriere efectiva.
|
||||
5. **Scriere efectiva** — metoda `do_scrie_factura` a formularului de articole
|
||||
(`COMUN\clase\ofacturare.vc2:14067-14360`, dublata identic in `frm_facturare_articole2` la `:18090-18370`):
|
||||
- **`pack_facturare.scrie_proforma(...)`** (:14071) — doar daca `poDate.eProforma=1`: scrie **doar in
|
||||
VANZARI**, explicit comentat in cod "*nu si in contabilitate*" (:14070).
|
||||
- **`pack_facturare.scrie_factura_avize(...)`** (:14103) — cand `poDate.Tip = 4` (facturare din aviz).
|
||||
- **`pack_facturare.scrie_factura2(...)`** (:14130, :14158) — comenzi (tip 3/21/25/28/42/47) sau alte tipuri
|
||||
("otherwise": lista de preturi etc.).
|
||||
- Toate aceste RPC-uri scriu in **VANZARI** si intorc un **cursor de verificare** (`lcCursorVerificare`)
|
||||
cu liniile de nota contabila propuse: coloane `id_act, ascd, ascc (asociat cont debit/credit), id_partd,
|
||||
id_partc, dataactt, datairegt, datascadt, suma` (:14184-14206).
|
||||
- Daca exista randuri in cursorul de verificare (adica nu e proforma), se afiseaza **`Do Form verificare`**
|
||||
(:14189) — utilizatorul poate corecta clientul (`id_partd/id_partc`) sau explicatiile (`ascd/ascc`) inainte
|
||||
de a confirma nota.
|
||||
- **`pack_facturare.finalizeaza_scriere_verificare(...)`** (:14247, si varianta `scrie_factura_avize_retur`
|
||||
la :14219/:14282 pentru retur-uri) — **aici se scriu efectiv notele contabile si rulajele** in Oracle,
|
||||
folosind eventualele corectii din formularul de verificare (`pcSirDifAcont`, `pcSirDifPart`).
|
||||
6. **Alte efecte laterale**:
|
||||
- Incasare/bon fiscal: `poGeneratorNumere.verifica_numar(16, poDate.nr_incasare)` (:503), `listeaza_bon_fiscal(...)`
|
||||
(:528) — chitanta/incasare asociata facturii, daca `poDate.incasat <> 0`.
|
||||
- Listare: `listeaza_ofacturare()` (:535).
|
||||
- Stoc: pentru facturare directa din stoc exista un flux paralel, `oscrie_vanzare_din_stoc`
|
||||
(`COMUN\programe\ofacturare_stoc.prg:318-412`) — apeleaza `pack_facturare.initializeaza_date_factura`,
|
||||
`adauga_articol_factura_stoc`, `scrie_in_vanzari`, si la final
|
||||
`update vanzari set id_fact = pack_contafin.get_idFact() where id_vanzare = ?` (:412) — leaga VANZARI
|
||||
de identificatorul notei contabile (`id_fact`).
|
||||
|
||||
## 2. Fluxul de EDITARE factura existenta
|
||||
|
||||
Formular: `frm_facturi` (lista facturi emise), clasa `COMUN\clase\ofacturare_comun.vc2`.
|
||||
|
||||
- **`frm_facturi.do_modifica`** — `ofacturare_comun.vc2:4382-4482`.
|
||||
- Verifica intai daca factura e deja in `anaf_efactura` (vezi punct 5) si daca `sters=0`; altfel blocheaza
|
||||
editarea (:4476-4478, mesaj "Nu puteti face modificari pe inregistrarile sterse sau facturile trimise in
|
||||
eFactura!").
|
||||
- Deschide `frm_modifica_factura` (:4433) si la confirmare (`gnButon=1`) executa
|
||||
**`pack_facturare.modifica_date_factura(...)`** (:4443-4460) cu parametrii:
|
||||
`id_vanzare, id_ruta, id_delegat, id_agent, id_masina, dataora_exp, id_facturare, listare_detaliata,
|
||||
text_aditional, tip_saft, efactura, data_act, data_scad, numar_act, serie_act`.
|
||||
- **Formular `frm_modifica_factura`** (`ofacturare_comun.vc2:5096-5608`) — campurile editabile efectiv expuse in
|
||||
UI sunt: ruta, delegat, agent, masina, data/ora expeditie, tip facturare (`id_facturare`),
|
||||
listare detaliata, text aditional, tip SAFT, si (conditionat de `numar_act` nenul) data act/scadenta/numar
|
||||
act/serie act — vezi `Init` (:5570-5584) si handlerele `chkDataAct/chkNrAct/chkSerieAct` (:5592-5606).
|
||||
**NU exista pe formular campuri pentru client, data facturii, seria/numarul facturii sau articole/preturi** —
|
||||
acestea nu pot fi schimbate prin acest flux de editare.
|
||||
- **Confirmat: editarea NU atinge notele contabile si rulajele.** `modifica_date_factura` primeste doar
|
||||
campurile enumerate mai sus (metadate document/logistica), nu recalculeaza sume, nu scrie in tabelele de
|
||||
note (vezi punct 3) si nu apeleaza vreun echivalent `PACK_CONTAFIN`. Nicio referinta la `pack_contafin` in
|
||||
`do_modifica`.
|
||||
- Editarea explicatiei unui articol de pe factura: **`frm_modifica_articol_factura`**
|
||||
(`ofacturare_comun.vc2:5023-5095`, apelat din `frm_facturi.do_modifica_explicatie` :4484-4501) —
|
||||
apeleaza doar **`pack_facturare.modifica_explicatie_articol(id_vanzare_det, ...)`** (:5067) — schimba
|
||||
**doar textul explicatiei**, nu cantitatea/pretul/cont-ul. Nici aici nu se ating notele contabile.
|
||||
|
||||
## 3. Tabelele de note contabile si rulaje
|
||||
|
||||
Nu exista schema DBC/DDL in working copy (tabelele sunt Oracle, VFP le vede via view-uri `gcs.v...`). Din
|
||||
codul de stergere (`ofacturare_comun.vc2:4573-4614`) rezulta 3 seturi de date, toate filtrate pe
|
||||
`an, luna, cod` (cod = `vanzari.cod`, identificatorul facturii):
|
||||
|
||||
| View interogat (VFP) | Cursor local | Camp cheie randuri | Rol |
|
||||
|---|---|---|---|
|
||||
| `gcs.vact_tot` | `actactan` / cursor `v_act` | `id_act` (+ `id_set`, `id_fact`, `id_factd`, `id_factc`) | Note contabile (randuri debit/credit) |
|
||||
| `gcs.vrul_tot` | `rul_temp` / cursor `v_rul` | `id_rul` | Rulaje |
|
||||
| `gcs.vrul_obinv_tot` | `rul_temp_obinv` / cursor `v_rul_obinv` | `id_rul_obinv` | Rulaje obiecte de inventar |
|
||||
|
||||
**Cheia de legatura factura -> nota**: `VANZARI.id_fact` — populat la emitere prin
|
||||
`pack_contafin.get_idFact()` (`COMUN\programe\ofacturare_stoc.prg:412`) sau intors ca parametru OUT din
|
||||
`scrie_factura2`/`finalizeaza_scriere_verificare` (`?@poDate.nid_vanzare` e id_vanzare, nu id_fact — id_fact
|
||||
pare populat separat de pachetul Oracle). Pe partea de nota, actul (`ACT`) are coloanele `id_fact`/`id_factd`/
|
||||
`id_factc` folosite de `pack_documente.ReferinteDocumenteNota`/`ReferinteDocument`
|
||||
(`COMUN\programe\odocumente.prg:8-46`) pentru a detecta referinte incrucisate intre documente (facturi ce se
|
||||
storneaza/compenseaza reciproc).
|
||||
|
||||
**Camp de provenienta pentru regenerare**: nu exista un camp explicit gen `sursa_id_vanzare` pe ACT/RUL vizibil
|
||||
in cod VFP; legatura se face prin **`cod` (numarul facturii) + `an` + `luna`** — vezi filtrul identic in
|
||||
`do_sterge` (:4573,4587,4599). Asta e mecanismul deja folosit pentru "sterge tot ce apartine facturii X":
|
||||
select pe `vact_tot`/`vrul_tot`/`vrul_obinv_tot where cod = <cod factura> and an=... and luna=...`, apoi
|
||||
sterge. **E si "reteta" pentru un eventual regenereaza = sterge + refa**, cu observatia ca id-urile
|
||||
(`id_act`, `id_set`, `id_fact`, `id_factd`) trebuie citite inainte de stergere daca se doreste refacere
|
||||
identica (linia 4642-4647 le extrage din `actactan` inainte de a apela stergerea).
|
||||
|
||||
## 4. Operatie existenta de STORNARE / STERGERE factura (cheie pentru "regenereaza")
|
||||
|
||||
Da — **`frm_facturi.do_sterge`** (`COMUN\clase\ofacturare_comun.vc2:4503-4719`) e exact mecanismul
|
||||
"sterge nota + rulaje asociate unei facturi":
|
||||
|
||||
- Blocheaza daca luna e inchisa (`glLunaInchisa`, :4518-4520) sau daca factura e deja stearsa (:4531-4534) sau
|
||||
daca nu e din luna curenta (:4543-4546).
|
||||
- **Cale proforma**: `pack_facturare.sterge_proforma(?pnIdVanzare, ?gnIdUtil)` (:4550) — nu are note, deci
|
||||
stergere simpla din VANZARI.
|
||||
- **Cale factura/aviz reala**:
|
||||
1. Verifica blocaj de referinte: `ReferinteDocumenteNota(pnAn, pnLuna, lnCod)` (:4565,
|
||||
`COMUN\programe\odocumente.prg:8`, cheama `pack_documente.ReferinteDocumenteNota`) — daca documentul
|
||||
e referentiat de incasari/plati, blocheaza stergerea (:4567 mesaj "Documentul are referinte...").
|
||||
2. Interogheaza `vact_tot`/`vrul_tot`/`vrul_obinv_tot` (vezi punct 3) ca sa afle daca exista note/rulaje
|
||||
de sters (`llRul`).
|
||||
3. Daca exista randuri in `actactan`, deschide formularul **`verificare`** (:4620-4624) pentru
|
||||
confirmare vizuala a ce se sterge, apoi porneste tranzactie explicita (`SQLSetprop(...,"Transactions",2)`,
|
||||
:4625) si:
|
||||
- fie ruleaza `OSCRIE_IN_FISIERE(2,.F.,llRul)` + **`pack_contafin.finalizeaza_stergere_nota(luna, an,
|
||||
NULL, id_set, cod, id_fact, id_factd, id_util)`** (:4642-4653) — varianta cu rulaje/fisiere multiple,
|
||||
- fie apeleaza direct **`pack_facturare.sterge_factura(?pnIdVanzare, ?gnLuna, ?gnAn, ?gnIdUtil)`**
|
||||
(:4689) daca nu exista note/rulaje de tratat special.
|
||||
4. Commit/rollback explicit, apoi revine pe tranzactie automata.
|
||||
|
||||
Asta confirma ca infrastructura "sterge + reface" exista deja pe partea de stergere completa
|
||||
(`sterge_factura` + `finalizeaza_stergere_nota`); un flux de "regenerare la editare" ar putea reutiliza aceste
|
||||
doua RPC-uri Oracle inainte de a re-emite (echivalentul pasilor de la punctul 1, pasul 5) — dar acest
|
||||
lant nu exista azi cablat din formularul de editare (`do_modifica`).
|
||||
|
||||
## 5. `anaf_efactura.id_fact` — verificare "a fost deja trimisa in eFactura"
|
||||
|
||||
- **Citire/test**: `COMUN\clase\ofacturare_comun.vc2:4428-4432`, in `frm_facturi.do_modifica`:
|
||||
```
|
||||
lcSql = [select count(*) as lneFactura from anaf_efactura where id_fact = ] + ALLTRIM(STR(poRec.id_fact))
|
||||
llSucces = goExecutor.oSelecteaza2Value(m.lcSql, @pneFactura)
|
||||
If poRec.sters = 0 AND NVL(m.pnEFactura,0) = 0
|
||||
... permite editarea ...
|
||||
Else
|
||||
amessagebox("Nu puteti face modificari pe inregistrarile sterse sau facturile trimise in eFactura!", 48, "Atentie")
|
||||
```
|
||||
(`pnEFactura`/`pneFactura` sunt aceeasi variabila — VFP e case-insensitive.)
|
||||
- Expresia de test: **exista cel putin un rand in `ANAF_EFACTURA` cu `id_fact = VANZARI.id_fact` a facturii
|
||||
curente** => considerata "trimisa" => editare blocata.
|
||||
- **Scriere**: `VANZARI.id_fact` insusi e populat la emitere (`pack_contafin.get_idFact()`,
|
||||
`COMUN\programe\ofacturare_stoc.prg:412`, echivalent probabil si in `scrie_factura2`/
|
||||
`finalizeaza_scriere_verificare` pe partea Oracle, nevizibil in VFP). `ANAF_EFACTURA.id_fact` e populat:
|
||||
- la import de factura de achizitie (nu de emitere): `UpdateEFacturaIdFact`
|
||||
(`COMUN\programe\import_efactura.prg:229`) — `update anaf_efactura set id_fact = ?pnIdFact where id = ?pnId
|
||||
and NVL(id_fact,0)=0`.
|
||||
- manual din formular: `frm_import_efactura.importmodifica` (`anaf_efactura.vc2:12930`) —
|
||||
`UPDATE crsFacturi SET id_fact = m.pnIdFact WHERE id = m.lnIdEfactura`.
|
||||
IPOTEZA: pentru facturile **emise** (nu achizitie), popularea `anaf_efactura.id_fact` la trimitere se face
|
||||
probabil in clasa server `AnafeFacturaServer`/`ANAFeFactura` (`COMUN\programe\anaf_efactura.prg`, ex.
|
||||
`UpdateDbFactura` :2018-2155) sau in Oracle la generarea XML — nu am gasit un UPDATE explicit VFP care
|
||||
leaga `id_fact` la momentul trimiterii unei facturi emise; ar trebui verificat separat (posibil in PL/SQL).
|
||||
|
||||
## 6. Cele 4 tipuri de sursa — ramificare si diferente
|
||||
|
||||
Ramificarea e pe **`VANZARI.tip`** (camp numeric), vazuta in doua locuri, cu acelasi grupaj:
|
||||
|
||||
`ofacturare.prg:266-308` (populare cursor articole la pornirea emiterii) si
|
||||
`ofacturare.vc2:14067-14174` (`do_scrie_factura`, alegerea RPC-ului de scriere):
|
||||
|
||||
| Sursa | `tip` (exemple) | Cursor populare articole | RPC scriere |
|
||||
|---|---|---|---|
|
||||
| **Lista de preturi** | 1,5,7,10,22,23,29,45 (si 48/49 varianta `cursor_articole_k`) | `pack_facturare.cursor_preturi(...)` | `scrie_factura2` (ramura "otherwise") |
|
||||
| **Contract** | 2,6,26,52 | `pack_facturare.cursor_contract(...)` | `scrie_factura2` (ramura "otherwise") |
|
||||
| **Comanda** | 3,21,25,28,42,47 | `pack_facturare.cursor_comanda(...)` | `scrie_factura2` (ramura explicita `Inlist(poDate.Tip,3,21,25,28,42,47)`, cu intrebare "Doriti sa se inchida comanda?" :14122) |
|
||||
| **Aviz** | 4 | `pack_facturare.cursor_avize(...)` | `scrie_factura_avize` (ramura `poDate.Tip = 4`, cu intrebare "Doriti sa se inregistreze si avizul de retur?" :14091) |
|
||||
| (alte, mentionate de completitudine) | 27 (lucrare), 30 (aviz din NIR), 41 (transfer gestiune), 8/9/24 (retur) | `cursor_lucrare`/`cursor_aviz_nir`/`cursor_gestiune`/`cursor_retur` | variante `scrie_factura_avize_retur`/`scrie_proforma` |
|
||||
|
||||
**Ce difera intre ele ca note contabile/rulaje**: apelul final e mereu
|
||||
`pack_facturare.finalizeaza_scriere_verificare(...)` (sau `scrie_factura_avize_retur` pentru retururi) —
|
||||
adica *acelasi* punct final scrie notele, insa RPC-ul de scriere in VANZARI difera (`scrie_factura_avize` vs
|
||||
`scrie_factura2`), iar la comanda/aviz apar intrebari suplimentare de inchidere document sursa (comanda
|
||||
inchisa / aviz retur generat) care produc efecte laterale suplimentare pe langa nota standard. Diferentele
|
||||
fine intre `scrie_factura2` si `scrie_factura_avize` (ce conturi/rulaje difera intern) sunt in PL/SQL, deci
|
||||
**nu sunt vizibile in working copy**.
|
||||
|
||||
## 7. Blocaje deja existente la editare
|
||||
|
||||
- **Sters**: `poRec.sters = 0` obligatoriu (`ofacturare_comun.vc2:4432`).
|
||||
- **Trimis in eFactura**: `anaf_efactura` are rand cu acelasi `id_fact` (punct 5), acelasi test la
|
||||
`:4428-4432`.
|
||||
- **Luna inchisa** — nu e verificat in `do_modifica` (doar in `do_sterge`, :4518-4520, `If glLunaInchisa Return`).
|
||||
Deci editarea (schimbare ruta/delegat/etc.) **nu e blocata de luna inchisa**, doar stergerea. IPOTEZA/
|
||||
observatie pentru discutie: daca se adauga regenerare de note la editare, ar trebui adaugat si acest test.
|
||||
- **Nu e din luna curenta** — verificat doar la stergere (:4543-4546: `(pnAn*12)+pnLuna <> (gnAn*12)+gnLuna`),
|
||||
nu la editare.
|
||||
- **Referinte incasari/plati** — verificat doar la stergere (`ReferinteDocumenteNota`, :4565-4569), nu la
|
||||
editare.
|
||||
- **Are chitanta/incasare** — nu am gasit un blocaj explicit *la editare*; la stergere, verificarea de mai
|
||||
sus (`ReferinteDocumenteNota`) acopera implicit si incasarile asociate (comentariul din
|
||||
`odocumente.prg:5-6` mentioneaza "id_fact ... pe id_factd sau id_factc").
|
||||
|
||||
## 8. Numerotare si documente legate — ce se poate schimba la editare
|
||||
|
||||
- **Client**: NU se poate schimba din `frm_modifica_factura` (formularul nu are control pentru client/partener;
|
||||
vezi punct 2). Singurul loc unde apare o restrictie explicita despre client e in fluxul de **verificare la
|
||||
emitere** (nu la editare): `ofacturare.vc2:14198-14201` — daca diferenta de partener ar afecta un cont "41*",
|
||||
`amessagebox("Nu puteti modifica clientul!",48,"Atentie") / Return .F.` — asta e in timpul emiterii
|
||||
(formularul `verificare`), nu in editarea ulterioara.
|
||||
- **Data facturii / seria / numarul facturii**: NU sunt pe formularul de editare. Campurile editabile
|
||||
`data_act`/`numar_act`/`serie_act` (:4444-4458) sunt pentru "act" (document justificativ atasat, cu
|
||||
checkbox-uri de activare `chkDataAct/chkNrAct/chkSerieAct`), NU seria/numarul facturii insesi — acelea
|
||||
se aloca ireversibil la emitere prin `poGeneratorNumere` (punct 1, pasul 2).
|
||||
- **Articole**: nu se pot modifica cantitati/preturi din fluxul de editare a facturii — doar explicatia unui
|
||||
articol (`frm_modifica_articol_factura`, punct 2).
|
||||
- **Aviz consumat / comanda / contract — flag "facturat"**: Exista camp **`facturat`** pe `COMENZI`
|
||||
(folosit in UI: `ct_comenzi.actualizeaza_grid1` colora randurile cu `facturat=1`,
|
||||
`COMUN\clase\ocomenzi.vc2:1144`; verificari `loRec.facturat=1` in `do_modifica`/`do_sterge` ale comenzii,
|
||||
:1806, :2065 — blocheaza modificarea/stergerea comenzii deja facturate; filtru rapid `facturat=1`/`facturat=0`
|
||||
in criterii, :2178). Marcarea `facturat=1` se face insa pe partea Oracle (`pack_facturare.scrie_factura2`
|
||||
etc.), nu am gasit un `UPDATE comenzi SET facturat` direct in VFP — deci **nu e vizibil in working copy**
|
||||
exact unde se scrie flagul, doar ca e citit/verificat pe partea VFP. Nu am gasit camp echivalent `facturat`
|
||||
pe AVIZE sau CONTRACTE in codul cercetat (posibil alt nume de camp sau tot pe partea Oracle) —
|
||||
IPOTEZA/necesita cautare suplimentara daca devine relevant.
|
||||
|
||||
---
|
||||
|
||||
## Rezumat concluzii cheie
|
||||
|
||||
1. **Emiterea** (`COMUN\programe\ofacturare.prg:90` -> `factureaza2`) scrie in VANZARI via
|
||||
`pack_facturare.scrie_proforma/scrie_factura_avize/scrie_factura2`, apoi (daca nu e proforma) afiseaza
|
||||
formularul `verificare` cu liniile de nota propuse si finalizeaza notele/rulajele prin
|
||||
**`pack_facturare.finalizeaza_scriere_verificare`** (`COMUN\clase\ofacturare.vc2:14247`, variante retur la
|
||||
:14219/:14282). Ramificarea pe cele 4 surse (lista preturi/contract/comanda/aviz) e pe `VANZARI.tip`,
|
||||
vezi tabel la punctul 6.
|
||||
2. **Editarea existenta** (`frm_facturi.do_modifica`, `COMUN\clase\ofacturare_comun.vc2:4382-4482`, via
|
||||
`pack_facturare.modifica_date_factura`) atinge DOAR metadate logistice (ruta/delegat/agent/masina/
|
||||
dataora_exp/tip_facturare/listare_detaliata/text_aditional/tip_saft/data-numar act) — **confirmat: nu
|
||||
atinge notele contabile/rulajele si nu recalculeaza sume**. Nu se pot schimba client, data/serie/numar
|
||||
factura sau articole.
|
||||
3. Notele contabile si rulajele stau in view-urile Oracle `vact_tot`/`vrul_tot`/`vrul_obinv_tot`
|
||||
(chei `id_act`/`id_rul`/`id_rul_obinv`), legate de factura prin **`cod`+`an`+`luna`** (identic filtru
|
||||
folosit si la stergere) si prin `VANZARI.id_fact` (populat de `pack_contafin.get_idFact()`).
|
||||
4. **Exista deja** un flux complet de stergere-cu-note: `frm_facturi.do_sterge`
|
||||
(`ofacturare_comun.vc2:4503-4719`) — foloseste `pack_facturare.sterge_factura` +
|
||||
`pack_contafin.finalizeaza_stergere_nota`, cu verificare de referinte (`ReferinteDocumenteNota`) si
|
||||
confirmare vizuala (formularul `verificare`). **Aceasta e infrastructura direct reutilizabila pentru un
|
||||
"regenereaza = sterge + refa"** al notelor/rulajelor la editare.
|
||||
5. **`anaf_efactura.id_fact`**: testul de blocare editare e `select count(*) from anaf_efactura where
|
||||
id_fact = <vanzari.id_fact_curent>` (`ofacturare_comun.vc2:4428-4432`) — daca exista rand, factura e
|
||||
considerata trimisa si editarea e refuzata. Punctul exact unde se scrie `anaf_efactura.id_fact` la
|
||||
trimiterea unei facturi **emise** nu e vizibil in VFP (ipoteza: pe partea Oracle/XML) — cel gasit in VFP e
|
||||
doar pentru facturi de achizitie importate.
|
||||
6. **Blocaje actuale**: editarea verifica doar `sters=0` si `netrimisa in eFactura`; NU verifica luna
|
||||
inchisa, NU verifica "luna curenta", NU verifica referinte incasari/plati — aceste 3 verificari exista
|
||||
doar pe fluxul de **stergere**, nu si pe cel de **editare** (`do_sterge` vs `do_modifica`,
|
||||
`ofacturare_comun.vc2:4503` vs `:4382`).
|
||||
7. Cod PL/SQL Oracle (`PACK_FACTURARE`, `PACK_CONTAFIN`, `PACK_DOCUMENTE`) nu e in working copy — doar
|
||||
semnaturile RPC apelate din VFP sunt vizibile local.
|
||||
85
docs/cercetare/rec_s4_runda1.md
Normal file
85
docs/cercetare/rec_s4_runda1.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# S4 runda 1 (PAGE3, doar afisare) — raport de inchidere, 08.08.2026
|
||||
|
||||
Runda 1 din S4 (`plan_06_s4_proiectare.md`) e **inchisa**. Diff-ul de review:
|
||||
diff aplicat (sters). Istoricul complet al sesiunii de implementare/depanare:
|
||||
`docs\cercetare\handoff_s4_runda1.md`, handoff intermediar (sters),
|
||||
handoff intermediar (sters).
|
||||
|
||||
## Ce s-a implementat
|
||||
|
||||
- **`COMUN\programe\ofacturare_editare.prg`**, doua functii noi la coada fisierului:
|
||||
- `IncarcaVanzareNota(tnCod, tnNract, tcSerieAct, tdDataAct)` — gaseste randul din `VANZARI`
|
||||
corespunzator notei curente (filtru compus `cod+nract+serie_act+data_act`, necesar pentru ca
|
||||
`VANZARI.COD` nu e unic — vezi `progres.md`), cursor `tvanz`.
|
||||
- `IncarcaArticoleFactura(tnIdVanzare)` — liniile active (`sters=0`) din `VANZARI_DETALII`, cu
|
||||
denumire articol/gestiune/valuta pentru afisare, cursor `tvd`.
|
||||
- **`COMUN\clase\omodificari.vc2`** (`frm_modific2024`):
|
||||
- `pgfArticole.PageCount = 3`, `PAGE3.Caption = "Articole factura"`.
|
||||
- Grid nou `pgfArticole.PAGE3.grdArticoleFactura` — 13 coloane, `RecordSource="tvd"`, strict
|
||||
**readonly** (grid + fiecare `Text1`).
|
||||
- Proprietati noi pe clasa: `lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare`.
|
||||
- `Load()`: placeholder `CREATE CURSOR tvd (...)` — evita dialogul nativ "Open" pe `RecordSource`
|
||||
inexistent la constructia grid-ului.
|
||||
- `Show()`: bloc nou care cheama `IncarcaVanzareNota`/`IncarcaArticoleFactura` si comuta
|
||||
`pgfArticole.PageCount` intre 2 si 3 dupa `lAreArticoleVanzari`.
|
||||
|
||||
Write-back facut, text si binar sincronizate, fidelity-check OK.
|
||||
|
||||
## Ce s-a testat, si cu ce rezultat
|
||||
|
||||
Suita `COMUN\utile\Teste\editare_factura\test_page3_articole.prg`, headless sub
|
||||
`COMUN\utile\Teste\watchdog_vfp.ps1 -AutoDismiss`, conexiune reala `MARIUSM_AUTO`. Rulare finala
|
||||
(dupa curatarea instrumentatiei de depanare): **exit code 0, 0 dialoguri, toate cazurile PASS**
|
||||
(`test_page3_articole_log.txt`):
|
||||
|
||||
| Caz | Verifica | Rezultat |
|
||||
|---|---|---|
|
||||
| `cod=1140888` (factura, tip=1) | `IncarcaVanzareNota`/`IncarcaArticoleFactura` direct: `id_vanzare=1050`, `tip=1`, 4 linii | PASS |
|
||||
| `cod=1140885` (tip=-12, cu rand VANZARI) | idem: `id_vanzare=1047`, `tip=-12` (detectia merge pe orice tip, nu doar facturi) | PASS |
|
||||
| `cod=1125486` (nota fara rand VANZARI) | 0 randuri gasite (caz negativ) | PASS |
|
||||
| `cod=1139934` (4 randuri VANZARI cu acelasi cod) | filtrul compus gaseste exact `id_vanzare=882` pe `nract=375/SSS`, 0 randuri pe `nract=13` (fara corespondent) | PASS |
|
||||
| `cod=1140888`, `frm_modific2024` instantiat (`Createobject`+`Show`, ca in `do_editare_factura`) | `PageCount=3`, `lAreArticoleVanzari=.T.`, `nIdVanzare=1050` | PASS |
|
||||
| `cod=1125486`, idem | `PageCount=2`, `lAreArticoleVanzari=.F.` | PASS |
|
||||
|
||||
Testul nu atinge Oracle in scriere — doar `SELECT`-uri prin `goExecutor`.
|
||||
|
||||
## Ce NU e acoperit (ramane pentru runde ulterioare)
|
||||
|
||||
- **Editarea in memorie a liniilor din grid** — runda 2 din S4; grid-ul e strict readonly in
|
||||
aceasta runda, prin design (B.2 din plan, marcaje `_modificat`/`sters`/`id_vanzare_det=0`, nu e
|
||||
inceput).
|
||||
- **Inchiderea prin `do_renunt()`** — netestata, la fel ca in rundele S1-S3 (mediul minimal de
|
||||
test are `ControlCount=0` pe formularul principal `frm_facturi`, deci butoanele native nu sunt
|
||||
disponibile pentru a conduce fluxul complet prin UI).
|
||||
- **Fluxul UI complet prin "Terminat"** — netestat; `verifica_pagecount_form` instantiaza
|
||||
`frm_modific2024` direct (`Createobject`+`Show`), la fel ca `do_editare_factura`, dar nu conduce
|
||||
formularul pana la inchidere. Validarea din `inainte_de_do_termin` nu e exercitata de aceasta
|
||||
suita.
|
||||
- Garda **referinte** pe `do_editare_factura` ramane netestata izolat (fara candidat pozitiv in
|
||||
datele de test) — mostenit din rundele anterioare, nu specific rundei 1 din S4.
|
||||
|
||||
## Blocaje rezolvate in aceasta sesiune (istoric, nu de reluat)
|
||||
|
||||
Trei blocaje separate au impiedicat verificarea live inainte de aceasta runda de inchidere, toate
|
||||
documentate pe larg in handoff intermediar (sters) si `COMUN\docs\testare-ui-vfp.md`:
|
||||
|
||||
1. `DO ... WITH gnAn, gnLuna` pasa prin referinta si umbrea variabilele in procedurile apelate,
|
||||
provocand dialogul nativ "View Parameter" pe un `?gnAn` din SQL passthrough — remediat cu
|
||||
pasare prin valoare (`DO ... WITH (gnAn), (gnLuna)`).
|
||||
2. Harnessul de test (`test_init_env_auto.prg`) incarca implicit clasa `omodificari` din copia de
|
||||
lucru **ROACONT**, nu din fisierul editat — remediat cu `RELEASE CLASSLIB` + `SET CLASSLIB TO`
|
||||
pe calea completa a fisierului sub test.
|
||||
3. Proprietatile custom noi (`lAreArticoleVanzari`, `nIdVanzare`, `nTipVanzare`) lipseau din
|
||||
`*<DefinedPropArrayMethod>` (intrarile `*p:`) — regula era documentata doar pentru metode
|
||||
(`*m:`); aplicata acum si aici, in `COMUN\docs\flux-editare-vfp-text.md`.
|
||||
|
||||
## Curatare facuta la inchidere
|
||||
|
||||
- Scoase liniile de log `[BISECT]` si `[DIAG]` (`ClassLibrary`/`PEMSTATUS`) din
|
||||
`test_page3_articole.prg` — erau instrumentatie de depanare, fara rol in suita finala. Corectiile
|
||||
de fond (parantezele pe `(gnAn)`/`(gnLuna)`, blocul `RELEASE CLASSLIB`/`SET CLASSLIB TO`) au
|
||||
ramas, cu comentariile lor.
|
||||
- Sters `test_baseline_isolation.prg` (+ `.FXP` + log) — era temporar prin design; concluzia lui
|
||||
("binarul original nu are blocajul") s-a dovedit nula, testa copia ROACONT a clasei, nu fisierul
|
||||
editat (vezi capcana #2 de mai sus).
|
||||
- Suita rulata din nou dupa curatare — PASS pe toate cazurile (tabelul de mai sus).
|
||||
231
docs/cercetare/rec_s5_oracle_vanzari.md
Normal file
231
docs/cercetare/rec_s5_oracle_vanzari.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Cercetare Oracle S5/S6/S7 (plan #6 - editare factura emisa)
|
||||
|
||||
Sursa: export proaspat din `MARIUSM_AUTO@ROA_CENTRAL` (`all_source`), facut in aceasta sesiune
|
||||
(08.08.2026), NU copia veche de pe disc din martie. Inainte de export s-a verificat tabela
|
||||
`VERSIUNE`: ultimul script aplicat e `ff_2026_08_06_12_COMUN_VANZARI_BACKFILL.sql`, identic cu
|
||||
`versiune_db.txt` din radacina proiectului (`2026_08_06_12`) - MARIUSM_AUTO e la zi.
|
||||
|
||||
Numerele de linie de mai jos sunt din exportul acestei sesiuni (`PACK_FACTURARE.pck` = 17010
|
||||
linii, `PACK_CONTAFIN.pck` = 9041 linii, `PACK_DOCUMENTE.pck` = 96 linii), NU din
|
||||
`ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql` de pe disc, care e vechi si nu contine modificarile de
|
||||
la #8. Fisierele exportate au ramas in scratchpad-ul sesiunii, nu sunt commise.
|
||||
|
||||
## A. Starea reala de azi
|
||||
|
||||
### A.1 `actualizeaza_vanzari` (PACK_FACTURARE.pck:16015-16025)
|
||||
|
||||
Confirmat: face EXCLUSIV realinierea `cod` + `STERS=0`, nimic altceva.
|
||||
|
||||
```
|
||||
UPDATE VANZARI_DETALII SET STERS = 0
|
||||
WHERE ID_VANZARE IN (SELECT ID_VANZARE FROM VANZARI WHERE COD = V_COD_VECHI);
|
||||
UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI;
|
||||
```
|
||||
|
||||
Important, nu era explicit in plan: al doilea `UPDATE` schimba `COD` pe ACELASI rand din `VANZARI`
|
||||
(filtrat dupa vechiul cod) - `ID_VANZARE` (cheia primara) NU se schimba niciodata la editare. Asta
|
||||
conteaza direct pentru sectiunea C. Comentariul inline de la `:16018` ("de modificat in caz ca il
|
||||
las sa stearga manual inregistrari din VANZARI_DETALII") confirma ca extinderea era anticipata.
|
||||
|
||||
### A.2 `PACK_CONTAFIN.finalizeaza_modificare_nota` / `finalizeaza_stergere_nota`
|
||||
|
||||
`finalizeaza_modificare_nota` (PACK_CONTAFIN.pck:8601-8651): cheama
|
||||
`pack_facturare.actualizeaza_vanzari(tnCod, lnCodNou)` DOAR daca
|
||||
`SELECT COUNT(*) FROM vanzari WHERE cod = tnCod` > 0 (`:8613-8617`). Daca `cod`-ul vechi nu exista
|
||||
in `vanzari` (nota nu vine dintr-o factura), pasul e sarit tacut, fara eroare - comportament corect
|
||||
pentru orice alt tip de nota (ROAGEST/ROACONT).
|
||||
|
||||
`finalizeaza_stergere_nota` (`:8653-8709`) e simetric: cheama `sterge_din_vanzari` doar daca
|
||||
`cod`-ul exista in vanzari. Spre deosebire de `actualizeaza_vanzari`,
|
||||
`sterge_din_vanzari` (PACK_FACTURARE.pck:16027-16042) cauta `ID_VANZARE` dupa `cod` si, daca nu-l
|
||||
gaseste, ridica `RAISE_APPLICATION_ERROR(-20000, ... FACT-017 ...)` - dar acest caz nu poate aparea
|
||||
in fluxul normal, fiindca apelul e deja gardat de count-ul de mai sus.
|
||||
|
||||
### A.3-A.4 `scrie_in_vanzari` (PACK_FACTURARE.pck:13491-13956) - formula de calcul
|
||||
|
||||
Extrasa integral (bloc `SELECT INTO` la `:13763-13931`, `UPDATE VANZARI` la `:13933-13949`).
|
||||
Coloane denormalizate scrise: `discount_tva, valoare_achizitie, total_fara_tva, total_tva,
|
||||
total_cu_tva, valval, tvaval, totval, id_valuta, curs, multiplicator, serie_incasat, nr_incasat,
|
||||
suma_incasat, tip_incasat` (14 coloane).
|
||||
|
||||
Sursa datelor: **`VANZARI_DETALII_TEMP`** (tabela globala temporara de sesiune, NU
|
||||
`VANZARI_DETALII`) + `VANZARI_SETURI_TEMP` (pentru linii-set) + `VANZARI_CURSURI` (deja scrisa cu
|
||||
`id_vanzare`-ul curent, la `:13704`, prin `scrie_cursuri`). `V_DISCOUNT_FACTURA` e parametru
|
||||
explicit al procedurii (discountul de pe document), nu o coloana citita din `VANZARI`.
|
||||
|
||||
Formula per-linie e deja extrasa in doua FUNCTII REUTILIZABILE, pure, cu parametri expliciti -
|
||||
NU inline: `calculeaza_total_fara_tva_fact` / `calculeaza_total_tva_fact`
|
||||
(PACK_FACTURARE.pck:15858-16013). Ambele ramifica explicit pe `V_PRET_CU_TVA` (`:15871`, `:15976`),
|
||||
apeland `calculeaza_total_cu_tva_fact` cand flagul e 1. Deci **partea de calcul per linie e deja
|
||||
partajabila** - nu trebuie reinventata.
|
||||
|
||||
Ce NU e extras e blocul de AGREGARE (suma pe toate liniile documentului, tratarea liniilor din
|
||||
seturi, alegerea `curs`/`multiplicator`/`id_valuta` din `vanzari_cursuri`), care e inline in
|
||||
`scrie_in_vanzari` si depinde de:
|
||||
- `VANZARI_DETALII_TEMP` ca sursa (nu `VANZARI_DETALII`);
|
||||
- stare de sesiune pe pachet: `pack_facturare.nin_valuta`, `ndiscount_evidentiat`,
|
||||
`cserie_act_incasare` / `nnumar_act_incasare` / `nsuma_incasare` / `ntip_doc_incasare` (acestea
|
||||
din urma populate DOAR la emiterea unei facturi-cu-incasare combinata - nu au sens la o editare
|
||||
ulterioara, vezi C);
|
||||
- `pack_def.GetIdMonedaNationala()`.
|
||||
|
||||
**Cine mai apeleaza `scrie_in_vanzari`**: doar 2 locuri, ambele INSERT-then-populate cu
|
||||
`RETURNING ID_VANZARE`: `scrie_proforma` (`:5643`) si `finalizeaza_factura` (`:14790`). Extragerea
|
||||
blocului de agregare intr-o functie interna parametrizata pe sursa (TEMP la emitere,
|
||||
`VANZARI_DETALII` real la editare) e SIGURA fata de ambii apelanti, daca varianta "sursa=TEMP"
|
||||
pastreaza exact comportamentul actual.
|
||||
|
||||
**Risc concret de reutilizare naiva pentru S5**: coloanele `serie_incasat/nr_incasat/
|
||||
suma_incasat/tip_incasat` NU trebuie recalculate la editare - vin din stare de sesiune specifica
|
||||
emiterii unei facturi-cu-incasare, fara nicio sursa persistenta din care sa fie reconstruite la o
|
||||
editare ulterioara. O procedura noua care ar copia tot `UPDATE`-ul din `scrie_in_vanzari` le-ar
|
||||
suprascrie cu `NULL` la fiecare editare, rupand legatura cu incasarea. Procedura propusa pentru S5
|
||||
trebuie sa scrie DOAR cele 11 coloane de totaluri/curs, nu si aceste 4.
|
||||
|
||||
### A.5 Flagul `VANZARI_DETALII.PRET_CU_TVA`
|
||||
|
||||
Deja parte a calculului in Oracle, nu doar in VFP: valoarea intra in agregare din
|
||||
`a1.pret_cu_tva <- vd.pret_cu_tva <- VANZARI_DETALII_TEMP.PRET_CU_TVA` pentru liniile directe
|
||||
(`:13883`) sau din `VANZARI_SETURI_TEMP.pret_cu_tva` pentru liniile din seturi (`:13909`), apoi
|
||||
`calculeaza_total_fara_tva_fact`/`calculeaza_total_tva_fact` ramifica pe el (`:15871`, `:15976`).
|
||||
Orice extragere pentru S5 trebuie sa citeasca acelasi `VANZARI_DETALII.PRET_CU_TVA` per linie (deja
|
||||
persistat, cf. #7) - nu exista alta sursa de adevar pentru el.
|
||||
|
||||
## B. Propunerea pentru S5
|
||||
|
||||
### Varianta A vs B
|
||||
|
||||
`actualizeaza_vanzari` e apelata NECONDITIONAT de `finalizeaza_modificare_nota` pentru ORICE
|
||||
editare de nota al carei `cod` exista in `vanzari` - nu doar facturi din #6, ci orice tip de
|
||||
document din VANZARI editat azi prin `frm_modific2024`/`afisjurcom.do_modifica`, folosit deja de
|
||||
ROAGEST/ROACONT in registrul jurnal. Daca **Varianta A** (extinderea in-place a lui
|
||||
`actualizeaza_vanzari` cu recalcul de totaluri) e aleasa, recalculul ar rula automat la ORICE
|
||||
editare de nota cu `cod` in vanzari, inclusiv editari care azi nu ating deloc sumele (ex. doar
|
||||
`explicatie`/`cont`). Risc de regresie: **mediu-mare**, extins la toata suita ROA, nu doar la #6.
|
||||
|
||||
**Varianta B** (procedura sora noua, ex. `recalculeaza_totaluri_vanzari`, apelata explicit din
|
||||
`finalizeaza_modificare_nota` doar cand editarea a atins efectiv `VANZARI_DETALII`): risc **mic** -
|
||||
`actualizeaza_vanzari` ramane neschimbata (zero impact pe restul aplicatiilor ROA care o folosesc
|
||||
azi), noua procedura e un pas suplimentar, opt-in.
|
||||
|
||||
**Recomandare: Varianta B**, motivata exclusiv de riscul de regresie asupra editarilor de note care
|
||||
NU sunt facturi si trec prin acelasi `finalizeaza_modificare_nota`.
|
||||
|
||||
### Schita procedurii propuse
|
||||
|
||||
```
|
||||
PROCEDURE recalculeaza_totaluri_vanzari(V_ID_VANZARE IN NUMBER) IS
|
||||
-- reia blocul de agregare din scrie_in_vanzari (:13763-13931), cu sursa
|
||||
-- VANZARI_DETALII WHERE ID_VANZARE = V_ID_VANZARE AND STERS = 0
|
||||
-- in loc de VANZARI_DETALII_TEMP; V_DISCOUNT_FACTURA citit din VANZARI.DISCOUNT
|
||||
-- (coloana existenta pe randul curent, populata la INSERT din acelasi parametru, :13682)
|
||||
BEGIN
|
||||
SELECT discount, ... INTO lnDiscountFactura, ... FROM vanzari WHERE id_vanzare = V_ID_VANZARE;
|
||||
-- acelasi SELECT agregat ca in scrie_in_vanzari, sursa VANZARI_DETALII in loc de _TEMP
|
||||
UPDATE vanzari
|
||||
SET discount_tva = ..., valoare_achizitie = ..., total_fara_tva = ...,
|
||||
total_tva = ..., total_cu_tva = ..., valval = ..., tvaval = ..., totval = ...,
|
||||
id_valuta = ..., curs = ..., multiplicator = ...
|
||||
-- FARA serie_incasat/nr_incasat/suma_incasat/tip_incasat, vezi A.4
|
||||
WHERE id_vanzare = V_ID_VANZARE;
|
||||
END;
|
||||
```
|
||||
|
||||
Apelata din `finalizeaza_modificare_nota` (PACK_CONTAFIN.pck:8601), dupa `actualizeaza_vanzari`.
|
||||
|
||||
### Mecanismul de scriere VFP in `VANZARI_DETALII` la emitere
|
||||
|
||||
Cautare in cache-ul text `COMUN` pentru `VANZARI_DETALII_TEMP` / `DETALII_TEMP` (case-insensitive):
|
||||
**niciun rezultat**, inclusiv in `oscrie_in_fisiere.prg`. Tabela temp e populata probabil printr-un
|
||||
helper generic de upload cursor->tabela (nume de tabela asamblat dinamic sau printr-un mecanism
|
||||
care nu apare ca literal in sursa text). Nu am putut confirma static calea exacta - de cercetat
|
||||
separat, pe partea VFP, inainte de proiectarea S4 (ce cursor/tabela temp foloseste editarea:
|
||||
reutilizeaza `VANZARI_DETALII_TEMP` sau una noua).
|
||||
|
||||
### Idempotenta si tranzactionalitate
|
||||
|
||||
Fluxul de editare a notei ruleaza deja intr-o singura tranzactie manuala:
|
||||
`Thisform.do_deschide_tranzactie()` (comun.vc2:2448) ... `oscrie_in_fisiere` + apelul catre
|
||||
`finalizeaza_modificare_nota` (`:2484-2486`) ... `Thisform.do_inchide_tranzactie(...)` (`:2535`,
|
||||
`COMMIT`/`ROLLBACK` in functie de succes). Procedura noua, apelata DIN INTERIORUL
|
||||
`finalizeaza_modificare_nota`, mosteneste automat aceeasi tranzactie: la esec pe jumatate,
|
||||
`ROLLBACK`-ul anuleaza tot (nota + `vanzari` + noile totaluri) - nu exista fereastra de
|
||||
inconsistenta. `recalculeaza_totaluri_vanzari` propusa e ea insasi idempotenta ca `UPDATE ... WHERE
|
||||
id_vanzare = :id` (poate rula de mai multe ori pe acelasi id fara efect cumulativ, spre deosebire
|
||||
de un `INSERT`).
|
||||
|
||||
## C. S6 - legaturile care depind de `cod`
|
||||
|
||||
Verificat pe cod (nu date, dat fiind ca `MARIUSM_AUTO` are date de test):
|
||||
|
||||
| Legatura | Cheie reala | Ramane valida? |
|
||||
|---|---|---|
|
||||
| `vanzari_coresp` | `ID_VANZARE_FACT`/`ID_VANZARE_AVIZ` (coloane confirmate din `all_tab_columns`) | DA - `actualizeaza_vanzari` nu schimba `ID_VANZARE` (vezi A.1), doar `COD` pe acelasi rand |
|
||||
| `facturat` (aviz/comanda, `marcheaza_facturat`) | `ID_VANZARE`, exclusiv (PACK_FACTURARE.pck:15384-15421, foloseste doar `pack_facturare.nid_vanzare`, niciodata `cod`) | DA |
|
||||
| chitanta/incasare (`SERIE_INCASAT`/`NR_INCASAT`/`SUMA_INCASAT`/`TIP_INCASAT`) | denormalizate direct pe randul `VANZARI` (nu FK), scrise o singura data la `scrie_in_vanzari` (`:13777-13780`) | DA - `actualizeaza_vanzari` nu le atinge; raman la valoarea de la emitere, ceea ce e comportamentul corect (nu se recalculeaza la editare de sume, vezi A.4/B) |
|
||||
| `ReferinteDocumenteNota`/`ReferinteDocument` (PACK_DOCUMENTE.pck:25-93) | `ACT.id_factd`/`id_factc` = `id_fact`-ul documentului, filtrat `cod <> tnCod` | DA - e o VERIFICARE PRE-EDITARE (gate, apelata inainte sa se schimbe ceva), nu o legatura persistenta; nu depinde de `cod`-ul rezultat dupa editare |
|
||||
| `anaf_efactura` | `ID_FACT` (coloana confirmata) | DA - `id_fact` nu se schimba la editare (by design, deja stabilit in plan) |
|
||||
| `documente` | `DOCUMENTE.ID_DOC = ACT.ID_FACT` (confirmat din `MERGE INTO DOCUMENTE ... ON A.ID_DOC = B.ID_DOC` unde `B.ID_DOC` vine din `ACT_TEMP.ID_FACT`, PACK_CONTAFIN.pck:858-880) | DA - si se REACTUALIZEAZA automat (SERIE_ACT/NRACT/DATAACT) la fiecare scriere de nota, inclusiv editare, prin acelasi `MERGE` care ruleaza in `cumuleaza_note_act` |
|
||||
|
||||
**Concluzie generala**: toate legaturile intre note contabile trec prin `ID_FACT`
|
||||
(`ACT.id_factd/id_factc`, `DOCUMENTE.id_doc`, `ANAF_EFACTURA.id_fact`), niciodata prin `cod`. Cum
|
||||
#6 pastreaza `id_fact` neschimbat by design, toate raman valide fara nicio interventie
|
||||
suplimentara. Singura legatura care trece prin altceva (`ID_VANZARE`, PK-ul randului `VANZARI`) e
|
||||
`vanzari_coresp`/`marcheaza_facturat`, si acesta ramane la fel neschimbat (doar `COD` se rescrie pe
|
||||
acelasi rand, vezi A.1). **S6 se poate inchide ca "confirmat pe cod, fara lucru suplimentar
|
||||
necesar"**, nu doar ca "de verificat".
|
||||
|
||||
## D. S7 - rotunjirea la reeditare
|
||||
|
||||
`verifica_total_document` (PACK_FACTURARE.pck:16073-...) insereaza o linie de corectie in
|
||||
`ACT_TEMP` cand suma recalculata din `ACT_TEMP` insusi (`V_TOTFTVA_VER`/`V_TOTTVA_VER`) difera de
|
||||
`pack_facturare.ntotftva`/`ntottva` (valori calculate in VFP, trecute prin stare de sesiune inainte
|
||||
de scriere). `ACT_TEMP` e complet repopulat la fiecare scriere - procedura nu "tine minte" nimic
|
||||
intre apeluri, in Oracle.
|
||||
|
||||
**Risc identificat pe partea VFP**: la editare, `afisjurcom.do_modifica` (comun.vc2:2352-2366)
|
||||
incarca in cursorul `tact` TOATE randurile curente ale notei
|
||||
(`SELECT * FROM vact_tot WHERE cod = ... [AND STERS = 0] ORDER BY id_act`) - asta include si linia
|
||||
de corectie inserata la salvarea anterioara (e un rand `ACT` normal, fara marcaj special care sa o
|
||||
distinga). La resalvare, acest rand curge inapoi prin `oscrie_in_fisiere` in `ACT_TEMP` impreuna cu
|
||||
liniile editate de utilizator, iar `verifica_total_document` ruleaza din nou pe noul `ACT_TEMP`
|
||||
(care deja contine vechea corectie).
|
||||
|
||||
**Nu se poate decide static** daca rezultatul e o a doua corectie suprapusa peste prima, sau daca
|
||||
mecanismul e self-consistent (adica `V_TOTFTVA_VER` deja include vechea corectie in suma agregata,
|
||||
iar `pack_facturare.ntotftva` recalculat de VFP coincide, deci nu se mai adauga nimic) - depinde de
|
||||
cum recalculeaza VFP `ntotftva`/`ntottva` la editare, cod care nu a fost verificat in aceasta trecere
|
||||
(afara de scope-ul Oracle-only al acestei cercetari).
|
||||
|
||||
De notat: acest mecanism NU e nou pentru #6 - e folosit azi neschimbat de orice editare de nota
|
||||
prin `do_modifica` (ROAGEST/ROACONT registru jurnal), pe orice tip de document, de ani de zile.
|
||||
Daca ar acumula corectii sistematic la editari repetate, ar fi deja o problema cunoscuta pe editari
|
||||
non-factura. Asta scade riscul, dar nu-l elimina pentru cazul specific facturii, unde S4/S5
|
||||
introduc o cale noua de a schimba cantitati/preturi care nu exista azi in `do_modifica` generic (pe
|
||||
notele generice azi de regula nu se schimba baza de calcul TVA in acelasi fel).
|
||||
|
||||
**Raspuns**: nu se poate decide din cod - testul deja propus in plan (S7: trei editari consecutive,
|
||||
verifica nr. de linii de corectie in `ACT`) e calea corecta si suficienta; nu exista scurtatura
|
||||
statica.
|
||||
|
||||
## E. Intrebari deschise
|
||||
|
||||
1. **Semnatura procedurii noi**: apel neconditionat din `finalizeaza_modificare_nota` oricand
|
||||
`cod`-ul exista in `vanzari` (simplu, cost mic - un `SELECT`+`UPDATE` in plus si pe editari care
|
||||
nu ating articolele), sau flag explicit `tnAtinsArticole` trecut din VFP (evita lucru inutil, dar
|
||||
risc de flag uitat/gresit)? **Recomandare: apel neconditionat** - cost neglijabil, elimina o
|
||||
clasa de bug.
|
||||
2. **Discountul de document la editare**: `V_DISCOUNT_FACTURA` la emitere e parametru explicit din
|
||||
VFP; planul S4/S4b nu mentioneaza editarea discountului de pe document, doar cantitate/pret/
|
||||
`pret_cu_tva` pe linie. La S5, discountul se citeste din `VANZARI.DISCOUNT` (neschimbat) - de
|
||||
confirmat cu Marius ca asta e comportamentul dorit (discountul de document ramane needitabil in
|
||||
#6).
|
||||
3. **`VALVAL`/`TVAVAL`/`TOTVAL`** (totaluri in valuta): planul S5 mentioneaza explicit doar
|
||||
`total_fara_tva`/`total_tva`/`total_cu_tva`/`valoare_achizitie`/`discount_tva`. Fac parte din
|
||||
acelasi bloc de agregare din `scrie_in_vanzari` - **recomandare: le includem in recalcul**, cost
|
||||
suplimentar zero, evita inconsistenta pe documentele in valuta straina.
|
||||
4. **Mecanismul de populare al `VANZARI_DETALII_TEMP`** la emitere n-a putut fi gasit static in
|
||||
cache-ul text `COMUN` (cautare literal, fara rezultate) - necesita cercetare VFP separata inainte
|
||||
de a proiecta calea exacta de scriere pentru editarea din S4 (aceeasi tabela temp, sau una noua).
|
||||
5. **S7**: raspunsul cere test dinamic (3 editari consecutive), nu poate fi confirmat static - de
|
||||
pastrat explicit in scope-ul S8, nu doar "de verificat" generic in text.
|
||||
198
docs/cercetare/retur_si_lista_preturi.md
Normal file
198
docs/cercetare/retur_si_lista_preturi.md
Normal file
@@ -0,0 +1,198 @@
|
||||
# Cercetare: retur din facturi anterioare + adaugare din lista de preturi pe document cu sursa
|
||||
|
||||
## A. Returul din facturi anterioare — cum functioneaza AZI
|
||||
|
||||
### A.1 `But_retur` — lant de apel
|
||||
|
||||
Definitia clasei butonului (`COMUN\clase\cmd_butoane.vc2:324-338`):
|
||||
```
|
||||
DEFINE CLASS but_retur AS buton OF "_cmd_base.vcx"
|
||||
caction = do_retur
|
||||
...
|
||||
Visible = .F.
|
||||
ENDDEFINE
|
||||
```
|
||||
Butonul e instantiat pe `frm_facturare_articole` la `COMUN\clase\ofacturare.vc2:11221-11227` (`Visible=.F.` implicit) si devine vizibil doar conditionat, in `Init`, la `ofacturare.vc2:15122-15127`:
|
||||
```
|
||||
Case Inlist(poDate.tip, 1, 5, 7, 10) && modificare v 2.0.56 : am adaugat 10
|
||||
&& facturare pe baza de lista de preturi
|
||||
...
|
||||
This.but_retur.Visible = .T. && pot sa fac retur de articole intr-o factura de vanzare
|
||||
```
|
||||
Deci butonul e vizibil **doar pe documente de tip 1/5/7/10** (facturare pe baza de lista de preturi), nu pe facturi de retur propriu-zise (8/9) si nu pe documente cu sursa contract/comanda.
|
||||
|
||||
`caction=do_retur` -> click apeleaza `frm_facturare_articole.do_retur` (`ofacturare.vc2:13963-13965`):
|
||||
```
|
||||
PROCEDURE do_retur
|
||||
Thisform.do_adauga_articol(.F., .F., .T.)
|
||||
ENDPROC
|
||||
```
|
||||
adica `do_adauga_articol(tlImplicit=.F., tlContract=.F., tlRetur=.T.)` (`frm_facturare_articole.do_adauga_articol`, `ofacturare.vc2:12813-12823`).
|
||||
|
||||
Pas cu pas in `do_adauga_articol` (`ofacturare.vc2:12813-12900`), ramura `tlRetur`:
|
||||
1. Utilizatorul selecteaza un articol (din `crsarticole`, lista de preturi) si o cantitate — `lnCantitate = poArticol.cantitate`.
|
||||
2. `Thisform.do_verifica_articol(...)` valideaza cantitatea.
|
||||
3. La `ofacturare.vc2:12881-12890` (case `Otherwise`, articol gestionabil):
|
||||
```
|
||||
OTHERWISE
|
||||
lnListaIdOld = poDate.listaid
|
||||
IF m.tlRetur
|
||||
loCauta = caut_facturi_multiple_client_articol(poDate.id_client, poDate.in_valuta, poDate.id_valuta, .F., poArticol.id_articol)
|
||||
If m.gnButon = 1 and !Empty(Nvl(loCauta.id_vanzare, 0))
|
||||
poDate.listaid = Alltrim(Str(loCauta.id_vanzare))
|
||||
* ID_ARTICOL:ID_VANZARE,ID_ARTICOL:ID_VANZARE
|
||||
thisform.cListaIdArticoleRetur = thisform.cListaIdArticoleRetur + IIF(!EMPTY(thisform.cListaIdArticoleRetur), ',', '') + ALLTRIM(STR(poArticol.id_articol)) + ':' + poDate.listaid
|
||||
Endif
|
||||
ENDIF
|
||||
llSucces = Thisform.do_alege_stoc(poArticol.id_articol, lnCantitate, poArticol.denumire, tlImplicit, poArticol.pretftva, poArticol.discount_unitar, loGrid, m.tlRetur)
|
||||
```
|
||||
4. `do_alege_stoc(..., tlRetur)` (`ofacturare.vc2:13200-13250`) foloseste ramura retur pentru a construi cursorul de stoc/gestiuni de unde se alege articolul de returnat:
|
||||
```
|
||||
If Inlist(poDate.tip,8,9,24) Or m.tlRetur && factura retur lei, factura retur valuta, aviz retur sau factura normala cu retur de articole
|
||||
lcSql = [{call pack_facturare.cursor_gestiuni_articol_retur(...)}]
|
||||
```
|
||||
5. Rezultatul e afisat printr-un formular `frm_articol_gest_factura` (`Createobject("frm_articol_gest_factura",tnCantitate,tlImplicit,tlRetur)` la `ofacturare.vc2:13353` si `:13361/:13375`), unde utilizatorul alege gestiunea/seria si cantitatea de returnat.
|
||||
6. La salvare, `do_scrie_articole` (`ofacturare.vc2:13967-13979`) trimite `poDate.listaid` (perechile `id_articol:id_vanzare` construite la pasul 3) catre `pack_facturare.initializeaza_date_factura`.
|
||||
|
||||
Concluzie: **nu exista un singur dialog "alege facturile sursa" pentru tot documentul** — selectia facturii sursa se face **per articol**, in momentul adaugarii fiecarei linii, printr-un dialog generic de cautare.
|
||||
|
||||
### A.2 Dialogul si criteriile de cautare a facturii sursa
|
||||
|
||||
Formularul e generic — functia `cauta_alfa` (dialog de picker standard ROA), invocata din `caut_facturi_multiple_client_articol` (`COMUN\programe\oproceduri_facturare.prg:2124-2159`):
|
||||
```
|
||||
Function caut_facturi_multiple_client_articol
|
||||
Lparameters tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple, tnIdArticol
|
||||
...
|
||||
lcSelect = [select a.serie_act,a.numar_act,a.data_act,a.dataora,a.id_vanzare ] + ;
|
||||
[from vanzari a join vanzari_detalii b on a.id_vanzare = b.id_vanzare and b.id_articol = ?pnIdArticol ]
|
||||
|
||||
lcFiltruOriginal = [a.sters=0 and a.tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) ] + gcCondSucursala + ;
|
||||
lcFiltruPart + lcFiltruValuta
|
||||
...
|
||||
lcTitlu = [Alegeti factura] && (tlFacturiMultiple = .F. la apelul din do_adauga_articol)
|
||||
loCauta = cauta_alfa(lcSelect, lcFiltru, lcSchema, lcOrder, lcColoane, lcTitlu, lcTitluColoane, lcNumeProc, llToateIreg, lcFiltruOriginal, lcPrimaColoana, lnPornire, lnTipReturn, lcIdColumn)
|
||||
```
|
||||
Coloane afisate: `Serie act, Numar act, Data, Data inreg.` Criterii de filtrare in interogare:
|
||||
- **articolul curent** (`b.id_articol = ?pnIdArticol`, obligatoriu — cautarea se face per-articol, nu la nivel de document);
|
||||
- **client** (`a.id_part = ?pnIdPart`, doar daca `tnIdPart` nu e gol — `lcFiltruPart`, `oproceduri_facturare.prg:2133`);
|
||||
- **valuta** (`a.in_valuta`/`b.id_valuta`, `lcFiltruValuta`, `:2134`);
|
||||
- **tip document sursa restrictionat la vanzari normale**: `a.tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)` — exclude explicit tipurile de retur (8,9,24), deci nu se poate face retur dintr-un retur.
|
||||
- **numar factura / perioada**: nu exista filtru SQL dedicat in interogare; `cauta_alfa` e dialogul generic de cautare alfa/browse al suitei (permite filtrare interactiva pe coloanele afisate, dar asta e comportament generic al dialogului, nu parametru specific — neverificat mecanismul intern de filtrare al `cauta_alfa`).
|
||||
|
||||
Rezultatul (`loCauta.id_vanzare`) e folosit ca `poDate.listaid` pentru articolul curent.
|
||||
|
||||
### A.3 Tipuri de document retur
|
||||
|
||||
Confirmat in cod, cu **3 valori**, nu doar 8/9 (comentariu explicit la `ofacturare.vc2:12862`):
|
||||
```
|
||||
* 8 = Retur lei, 9 = retur valuta sau factura de vanzare normala, dar cu retur de articole, 24 = aviz retur
|
||||
```
|
||||
Folosite consistent in tot `frm_facturare_articole`: `Inlist(poDate.tip, 8, 9, 24)` la `:13043`, `:13230`, `:13728`, `:14751`; `Inlist(poDate.tip, 8, 9)` separat la `:15236` (UI caption) si `:9718` (eliminare camp curs valutar). Pe `frm_facturare_articole2` (varianta "2" a formularului) aceleasi tipuri apar fara 24 in unele locuri (`:17531`: doar 8,9 — de verificat daca e omisiune sau intentionat, neclar din cod).
|
||||
|
||||
Semnul cantitatilor pe retur — la `do_alege_stoc` (`ofacturare.vc2:13273, :13300, :13309`):
|
||||
```
|
||||
Select Iif(poDate.tip=41,-1,1)*Sum(cantitate) As cantitate, ...
|
||||
...
|
||||
Where a.cantitate - Iif(Inlist(poDate.tip,8,9,24),(-1),1) * Nvl(b.cantitate,0) > 0
|
||||
```
|
||||
adica pentru tip 8/9/24 semnul cantitatii deja pe factura curenta se scade cu semn opus (`-1` in loc de `1`), consistent cu inregistrari de retur.
|
||||
|
||||
**Ziua de curs eliminata pe retur** — confirmat, dar linia corecta e in `COMUN\clase\ofacturare.vc2:9717-9722` (`frm_date_factura.Init`), NU in `ofacturare_comun.vc2` (fisierul citat in plan nu are randul respectiv — `ofacturare_comun.vc2` are doar 7432 de linii):
|
||||
```
|
||||
*!* modificare v 2.0.56
|
||||
If Inlist(poDate.tip, 8, 9)
|
||||
lnHeight = lnHeight - .clb_zi_curs.Height
|
||||
laPozitii(.clb_zi_curs.TabIndex, 2) = 1
|
||||
.RemoveObject('clb_zi_curs')
|
||||
Endif
|
||||
*!* modificare v 2.0.56 ^
|
||||
```
|
||||
Corolar in acelasi `Init` de `frm_facturare_articole` (`ofacturare.vc2:15098`): `Iif(!Inlist(poDate.tip, 8, 9), "(" + Dtoc(poDate.zi_curs) + ")", "")` — eticheta de curs valutar nu afiseaza data pe retur.
|
||||
|
||||
### A.4 Retur partial
|
||||
|
||||
Da, e posibil. Coloana de cantitate din `grd_articole`, pe tip 8/9, isi schimba explicit titlul in "cantitate maxima de returnat" (`ofacturare.vc2:15236-15239`):
|
||||
```
|
||||
Case Inlist(poDate.tip, 8, 9) && factura retur lei/valuta
|
||||
This.grd_articole.cCantitate.header1.Caption = [Cant. max. de returnat]
|
||||
This.cmesaj_cantitate = [Nu se mai poate face retur pentru acest articol!]
|
||||
```
|
||||
si analog pentru aviz retur (tip 24) la `:15242-15245`. Cantitatea introdusa de utilizator e validata in `do_verifica_articol` (`:14743-14754`):
|
||||
```
|
||||
llRetur = Inlist(poDate.tip,8,9,24)
|
||||
Do Case
|
||||
Case poArticol.gestionabil = 0 Or gnScadereStoc = 1 Or m.llFacturareFaraStoc
|
||||
llReturn = .T.
|
||||
Case (tnCantitate >= 0 And m.llRetur ) Or ...
|
||||
lcMesaj = Alltrim(Thisform.cmesaj_cantitate)
|
||||
amessagebox(lcMesaj,0+48,"Atentie")
|
||||
llReturn = .F.
|
||||
```
|
||||
adica sistemul respinge doar cazul in care cantitatea ramasa dupa retur ar iesi din domeniul valid (`tnCantitate >= 0` fiind conditia de eroare pe retur, unde cantitatile de retur sunt negative) — deci utilizatorul poate introduce orice cantitate <= maximul returnabil calculat de `cursor_gestiuni_articol_retur`, inclusiv mai mica (retur partial). Formularul de alegere (`frm_articol_gest_factura`, ramura `tlRetur`, `:13353-13385`) permite editarea cantitatii inainte de confirmare.
|
||||
|
||||
### A.5 Legatura stocata linie-de-retur -> linie originala
|
||||
|
||||
**Nu am gasit o coloana pe linie in `VANZARI_DETALII`** (de tip "id linie sursa") in codul VFP text disponibil. Ce exista, e legatura **la nivel de antet de document**, prin parametri output ai apelurilor Oracle:
|
||||
- `poDate.nid_vanzare` / `poDate.nid_vanzare_retur` — populati ca parametri `?@...` in `pack_facturare.scrie_factura_avize_retur(...)` (`ofacturare.vc2:14448`, `:14509`) si `pack_facturare.finalizeaza_scriere_verificare(...)` (`:14472`); resetati implicit la `oDateFactura.Reset` (`COMUN\programe\ofacturare_comun.prg:569`: `.nid_vanzare_retur = 9999999999`).
|
||||
- La nivel de sesiune VFP (nu persistat ca coloana confirmata), `thisform.cListaIdArticoleRetur` acumuleaza perechi `ID_ARTICOL:ID_VANZARE` (`ofacturare.vc2:12888`) trimise ca `poDate.listaid` catre `pack_facturare.initializeaza_date_factura` (`:13977-13979`) — asta e mecanismul prin care Oracle *primeste* info despre factura sursa per articol, dar daca acesta persista intr-o coloana dedicata pe `VANZARI_DETALII` (ex. id linie/document sursa) **nu se poate confirma din sursa VFP** — pachetul `pack_facturare` e in Oracle, in afara acestui repo. Cercetare existenta in `docs\cercetare\rec_cale_vanzari_detalii.md` si `rec_s5_oracle_vanzari.md` (interogari live pe schema Oracle, 08.08.2026) nu mentioneaza vreo coloana de tip `ID_DET_SURSA`/`ID_VANZARE_RETUR` pe `VANZARI_DETALII`. **Neverificat.**
|
||||
|
||||
---
|
||||
|
||||
## B. Adaugarea din lista de preturi pe document cu sursa (comanda/contract)
|
||||
|
||||
### A6/B6. Ce se vede azi pe cod
|
||||
|
||||
`frm_facturare_articole.Init`, `Do Case` pe `poDate.tip` (`ofacturare.vc2:15108-15248`) configureaza UI-ul per tip. Puncte relevante:
|
||||
|
||||
- **Contract (tip 2, 6)** — `ofacturare.vc2:15129-15143`:
|
||||
```
|
||||
Case Inlist(poDate.tip, 2, 6)
|
||||
&& facturare pe baza de contract
|
||||
This.lb_titlu_alb_b121.Caption = [FACTURA PE CTR. ] + Alltrim(poDate.descriere)
|
||||
This.grd_articole.RemoveObject('cSerie')
|
||||
&& articole din lista de preturi
|
||||
This.grd_articole.cCantitate.header1.Caption = [Cantitate in stoc]
|
||||
This.cmesaj_cantitate = [Acest articol nu este pe stoc!]
|
||||
```
|
||||
Comentariul "articole din lista de preturi" e explicit: `grd_articole` (cu butonul `But_urmator1`/`do_urmator`->`do_adauga_articol()`, cursorul `crsarticole`) **ramane activ si populat cu lista de preturi** chiar si pe document de tip contract. In paralel, `grd_contracte` (buton `But_urmator2`/`do_urmator2`->`do_adauga_articol(.F.,.T.)`, cursorul `crsarticole1`) afiseaza articolele restrictionate la contract.
|
||||
|
||||
Cei doi cursori se populeaza distinct: pentru `tnTip in (2,26,6,52)`, apelul e `pack_facturare.cursor_contract(...)` (`COMUN\programe\ofacturare.prg:283-290`), care umple atat `crsarticole` cat si (separat) `crsarticole1` (confirmat prin `Create Cursor crscontracte ... Select Distinct ... From (lcCursor + [1])`, `ofacturare.prg:433-440` — `lcCursor+'1'` = `crsarticole1` deja exista la acel punct).
|
||||
|
||||
- **Comanda (tip 3)** — `ofacturare.vc2:15144-15150`:
|
||||
```
|
||||
Case poDate.tip = 3
|
||||
&& facturare pe baza de comanda
|
||||
This.lb_titlu_alb_b121.Caption = [FACTURA LA COMANDA ] + Alltrim(poDate.descriere)
|
||||
This.grd_articole.RemoveObject('cSerie')
|
||||
This.grd_articole.cCantitate.header1.Caption = [Cantitate comandata]
|
||||
This.cmesaj_cantitate = [A fost facturata intreaga cantitate comandata pentru acest articol!]
|
||||
This.but_urmator_tot1.Visible = .T.
|
||||
```
|
||||
Aici `grd_articole`/`crsarticole` e populat direct de `pack_facturare.cursor_comanda(...)` (`ofacturare.prg:292-293`, tip in `(3,21,25,28,42,47)`) — adica **doar articolele comenzii**, nu lista de preturi libera. Nu exista al doilea cursor (`crsarticole1` nu se creeaza pentru tip 3, doar pentru `2,6,26` conform `ofacturare.prg:433`), deci in `Init` la `ofacturare.vc2:15294-15324`:
|
||||
```
|
||||
If !Used('crsarticole1')
|
||||
...
|
||||
Thisform.RemoveObject('grd_contracte')
|
||||
Thisform.RemoveObject('but_urmator2')
|
||||
Endif
|
||||
```
|
||||
grila si butonul secundar se elimina complet.
|
||||
|
||||
Exceptie: la **copiere de factura/aviz** (`poDate.lCopiere`), indiferent de tip, codul adauga explicit lista de preturi peste cursorul existent (`ofacturare.prg:454-473`):
|
||||
```
|
||||
IF m.llCopiere
|
||||
* Adaug in lista articolelor cursorul cu lista de preturi ca sa pot adauga si alte articole in factura copiata/modificata
|
||||
lcSqlCursor = [{call pack_facturare.cursor_preturi(...)}]
|
||||
...
|
||||
SELECT crsArticole
|
||||
APPEND FROM DBF(m.lcCursorTemp)
|
||||
ENDIF
|
||||
```
|
||||
|
||||
### B7. Unde e restrictia
|
||||
|
||||
Nu e un `Visible`/`Enabled` pe buton (butonul `But_urmator1`/lista-de-preturi exista si e vizibil pentru toate tipurile, la fel `grd_articole`) si nu e o validare la salvare — **restrictia e la nivelul continutului cursorului de articole disponibile** (`crsarticole`), decis de care procedura Oracle il populeaza in `factureaza()`/`factureaza2()` (`ofacturare.prg:266-308`, `Do Case` pe `tnTip`):
|
||||
- tip **1,5,7,10** (lista de preturi) si **2,6,26,52** (contract) -> `cursor_preturi`/`cursor_contract`: `crsarticole` contine lista de preturi completa (nerestrictionata la sursa) => **azi se poate deja adauga liber din lista de preturi pe un document contract**.
|
||||
- tip **3,21,25,28,42,47** (comanda) -> `cursor_comanda`: `crsarticole` contine **doar** articolele comenzii => **azi NU se poate** adauga o linie libera din lista de preturi pe un document comanda, decat prin copiere de document (`llCopiere`, ramura separata mai sus).
|
||||
|
||||
Concluzie B: distinctia plan-ului ("comanda sau contract, tipurile 2,6,26,52") nu e uniforma in codul actual — **contractul (2,6,26,52) are deja acces liber la lista de preturi** (al doilea grid `grd_contracte` e doar un adaos, nu o restrictie), in timp ce **comanda (3) e restrictionata strict la continutul comenzii**, fara optiune de adaugare libera in fluxul normal (doar la copiere de document).
|
||||
163
docs/cercetare/roaauto_articole_lista_preturi.md
Normal file
163
docs/cercetare/roaauto_articole_lista_preturi.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# ROAAUTO — adaugarea de articole reale din nomenclator pe langa MANOPERA/MATERIALE
|
||||
|
||||
## Corectie fata de `roaauto_facturi.md`
|
||||
|
||||
Raportul anterior a descris facturarea ROAAUTO ca fiind formata **doar** din linii sintetice
|
||||
(`-100000`..`-100008`). E incomplet: exista un mecanism separat, **"Alte servicii"**, care adauga
|
||||
linii cu `id_articol` real (pozitiv), din nomenclatorul de articole, in plus fata de liniile
|
||||
sintetice MANOPERA/MATERIALE. Mecanismul e cablat **direct in `factureaza_deviz`**, deci face
|
||||
parte din acelasi flux descris anterior, nu dintr-un flux alternativ.
|
||||
|
||||
## 1. Unde se adauga articole din nomenclator
|
||||
|
||||
Formular: `frm_incasare_finala` (`D:\ROA\ROAAUTO\Clase\oviz_devize.vc2:5435-7143`), aratat de
|
||||
`frm_emitere_facturi.do_factureaza_final` (`oviz_devize.vc2:2569` `ofrmincasare=Createobject('frm_incasare_finala',...)`)
|
||||
si `do_factureaza_final_part` (`:3539`) — adica **la momentul emiterii facturii finale**, nu pe
|
||||
formularul de deviz propriu-zis (`oviz_devize` nu are buton de adaugare articol la crearea
|
||||
devizului).
|
||||
|
||||
Metoda: `frm_incasare_finala.do_adauga` (`oviz_devize.vc2:6539-6580`):
|
||||
```
|
||||
lcXMLArticole = cauta_nom_articole([in_stoc = 0 and in_crm = 1 and id_articol not between -100008 and -100000])
|
||||
...
|
||||
Replace id_articol With loArticol.id_articol,denumire With loArticol.denumire,um With loArticol.um,codmat With loArticol.codmat,;
|
||||
id_lucrare With lnIdLucrare,nrord With lcNrOrd
|
||||
```
|
||||
Cauta in `vnom_articole_toate` (nomenclatorul de articole, cursor `crstmpart`) prin
|
||||
`cauta_nom_articole` (`COMUN\programe\ocautare.prg:1781`), filtrat la articole **fara gestiune**
|
||||
(`in_stoc = 0`) si **vizibile in CRM** (`in_crm = 1`), excluzand explicit id-urile sintetice
|
||||
(`not between -100008 and -100000`). Articolele alese se adauga in grila `grd_altele`
|
||||
(`oviz_devize.vc2:6060`, coloane `cDenumire/cNrord/cCantitate/cPretFTva/cValoareftva`,
|
||||
`oviz_devize.vc2:6121-6222`), sustinuta de cursorul `crsalteserv`, creat inainte de afisarea
|
||||
formularului in `frm_emitere_facturi.do_factureaza_final` (`oviz_devize.vc2:2553-2555`) si
|
||||
`do_factureaza_final_part` (`:3522-3524`):
|
||||
```
|
||||
Create Cursor crsalteserv(id_articol N(14) Not Null,denumire c(100) Not Null,codmat c(50) Null,um c(10) Null,;
|
||||
id_lucrare N(14) Not Null,nrord c(100) Not Null,;
|
||||
cantitate N(14,gnPCant) Default 1,pretftva N(14,gnPPretV) Default 0,valoareftva N(14,gnPc) Default 0)
|
||||
```
|
||||
**Important**: `do_adauga` NU completeaza `pretftva`/`cantitate` — raman la valorile implicite
|
||||
(1, respectiv 0). Pretul si cantitatea se **introduc manual** de operator in grila
|
||||
(`grd_altele.cPretFTva.Text1.LostFocus` / `cCantitate.Text1.LostFocus`, ambele apeland
|
||||
`Thisform.do_modifica_alteserv()` care recalculeaza `valoareftva = cantitate * pretftva`,
|
||||
`oviz_devize.vc2:6692-6698,7064-7070`). Nu exista o cautare/preluare automata de pret pentru
|
||||
aceste articole in acest flux (vezi punctul 6).
|
||||
|
||||
## 2. Cum ajung in factura — linii separate, NU cumulate
|
||||
|
||||
In `factureaza_deviz` (`Programe\oproceduri_devize.prg`), liniile sintetice MANOPERA/MATERIALE/
|
||||
DISCOUNT/AVANS se insereaza in cursorul `crsdeviz` (`:936-983`) cu id-uri fixe (`-100000`..
|
||||
`-100008`). La pasul de cumulare pentru optiunea "articol cumulat REPARATII AUTO" (`:999-1012`)
|
||||
se cumuleaza **doar** liniile cu `id_articol IN (-100003,-100000,-100002,-100001)` — liniile din
|
||||
`crsalteserv` nu sunt incluse in acest `INLIST`, deci nu pot fi absorbite in cumulare.
|
||||
|
||||
Liniile din `crsalteserv` se insereaza **separat**, dupa cumulare, cu `id_articol` real:
|
||||
```
|
||||
If Used('crsalteserv')
|
||||
Insert Into (lcCursorDeviz)(id_articol,denumire,explicatie,cantitate,um,id_jtva_coloana,proc_Tvav,taxcode,pret,discount_unitar) ;
|
||||
Select id_articol,denumire,"",cantitate,um,CAST(lnIdJTvaColoana as N(6) null) as id_jtva_coloana,...,pretftva,0 From crsalteserv Where !Deleted()
|
||||
Endif
|
||||
```
|
||||
(`oproceduri_devize.prg:1036-1041`) — deci id-ul real din `nom_articole` trece nemodificat.
|
||||
De acolo, fiecare rand ajunge in `crsvanztemp` (`:1190-1206`) si e scris in Oracle prin
|
||||
`pack_facturare.adauga_articol_factura_deviz` (`:1240-1257`, apelul SQL construit cu
|
||||
`Alltrim(Str(poArticol.id_articol))` la `:1241`), care insereaza direct in
|
||||
`VANZARI_DETALII_TEMP` cu `ID_ARTICOL = V_ID_ARTICOL` (pozitiv, real) — vezi implementarea in
|
||||
`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:4675-4745`. Confirmare: **articolele din "Alte
|
||||
servicii" raman linii proprii, cu id_articol real, si NU se aduna in liniile sintetice
|
||||
MANOPERA/MATERIALE.**
|
||||
|
||||
## 3. Gestiunea
|
||||
|
||||
`id_gestiune` in cursorul `lcCursorDeviz` are `DEFAULT null` (`oproceduri_devize.prg:885`) si
|
||||
**niciun** `INSERT INTO` — nici cel al liniilor sintetice, nici cel al liniilor din
|
||||
`crsalteserv` (`:1040-1041`) — completeaza aceasta coloana. La transferul in `crsvanztemp`
|
||||
(`:1201-1206`) coloana `id_gestiune` (definita fara valoare implicita explicita la `:1190`) nu e
|
||||
in lista `SELECT`, deci ramane 0 (implicit numeric), transmis ca literal `0` catre
|
||||
`adauga_articol_factura_deviz` (`:1255`, `Alltrim(Str(poArticol.id_gestiune))`), care il scrie ca
|
||||
atare in `VANZARI_DETALII_TEMP.ID_GESTIUNE` — nu NULL, ci **0**. Nu exista descarcare de gestiune
|
||||
pentru aceste linii; e consistent cu faptul ca articolele oferite spre alegere sunt filtrate
|
||||
explicit `in_stoc = 0` (articole/servicii fara gestiune de stoc). **Concluzie: liniile din "Alte
|
||||
servicii" NU au gestiune reala completata**, exact ca liniile sintetice — diferenta e doar
|
||||
`id_articol`.
|
||||
|
||||
## 4. Stergere / modificare inainte de facturare
|
||||
|
||||
- Stergere: `frm_incasare_finala.do_sterge` (`oviz_devize.vc2:6730-6741`), cu confirmare
|
||||
(`amessagebox("Sunteti sigur ca doriti sa stergeti articolul "+lcArticol+" de pe factura?",4+32,...)`),
|
||||
marcheaza randul `Delete` in cursorul bufferat (exclus apoi prin `Where !Deleted()` la punctul 2).
|
||||
- Modificare cantitate/pret: direct in grila (`grd_altele.cCantitate`/`cPretFTva`), vezi punctul 1.
|
||||
Toate aceste actiuni sunt posibile **doar cat timp `frm_incasare_finala` e deschis, inainte de
|
||||
`do_termin`** (validarea din `inainte_de_do_termin`, `:6749-6801`, verifica duplicate si valori 0,
|
||||
dar nu mai permite reintrarea in formular dupa emitere).
|
||||
|
||||
## 5. Dupa facturare — cmd_modifica1 si cai alternative
|
||||
|
||||
Confirmat, cu corectie de context: `Thisform.cmd_modifica1.Enabled=Iif(Empty(nrfact),.T.,.F.)`
|
||||
se afla in `frm_emitere_facturi.grdcomenzi.AfterRowColChange` (`oviz_devize.vc2:4536`), nu intr-o
|
||||
metoda separata — se re-evalueaza la fiecare schimbare de rand in grila de comenzi, dezactivand
|
||||
butonul "Modifica" de indata ce comanda are `nrfact` completat. Verificat suplimentar:
|
||||
- `frm_emitere_facturi.verifica_stornare` (`oviz_devize.vc2:4513-4514`) e **gol** (`PROCEDURE
|
||||
verifica_stornare / ENDPROC`, fara cod) — nu exista storno de comanda/deviz in acest formular.
|
||||
- Singurul "storno" gasit in `oviz_devize.vc2` e `do_storneaza_avans` (`:4256`, folosit din
|
||||
`do_factureaza_final`/`do_factureaza_final_part` la `:2345`/`:3305`) — priveste exclusiv
|
||||
reversarea unui **avans incasat**, nu redeschiderea unei facturi/deviz deja emise si nu permite
|
||||
adaugarea de articole pe o factura emisa.
|
||||
- Am gasit un mecanism generic, in afara `oviz_devize.vc2`, care poate **sterge complet** o
|
||||
factura deja emisa: `frm_facturi.do_sterge` (`COMUN\clase\ofacturare_comun.vc2:4649-4689`),
|
||||
care apeleaza `pack_facturare.sterge_factura(?pnIdVanzare,...)`. In Oracle,
|
||||
`sterge_factura` (`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:5441-...`) face un **soft-delete**
|
||||
(`UPDATE VANZARI SET STERS = 1 ...`, `:5505-5508`), cu verificari ca nu existe deja facturi/avize
|
||||
de retur legate. Aceasta e o stergere a intregii facturi (delete + re-emitere ulterioara),
|
||||
**nu** o adaugare/modificare de articole pe factura existenta.
|
||||
**Neverificat**: nu am putut confirma, in bugetul acestei cercetari, daca `frm_facturi` e
|
||||
cablat intr-un meniu ROAAUTO (grep-ul pentru instantieri `frm_facturi` in fisiere specifice
|
||||
ROAAUTO — in afara de `COMUN\` — nu a gasit potriviri de cod, doar metadate ascunse) si nici
|
||||
daca stergerea Oracle reseteaza `nrfact`/`facturat` pe comanda ROAAUTO de origine (ar necesita
|
||||
urmarirea `pack_auto`, in afara scriptului analizat). Deci **nu pot confirma sau infirma cu
|
||||
dovada directa** o cale completa "sterge factura -> reface devizul cu articole diferite" din
|
||||
interiorul ROAAUTO; pot confirma doar ca un asemenea instrument de stergere exista generic in
|
||||
suita si ca in `oviz_devize.vc2` nu exista niciun cod care sa modifice/adauge articole pe o
|
||||
factura deja emisa.
|
||||
- **Concluzie pe intrebarea centrala**: constatarea anterioara ramane valabila si dupa aceasta
|
||||
cercetare — `oviz_devize.vc2` insusi nu ofera nicio cale de a adauga/modifica articole pe o
|
||||
comanda/deviz cu `nrfact` completat; singura cale gasita spre o factura deja emisa e stergerea
|
||||
totala (soft-delete) prin ecranul generic COMUN, nu o editare in linie.
|
||||
|
||||
## 6. Nomenclator vs. lista de preturi — doua surse, cea folosita de ROAAUTO e nomenclatorul brut
|
||||
|
||||
Da, exista doua surse diferite in suita ROA:
|
||||
- **Nomenclatorul brut de articole**: `vnom_articole_toate` / `nom_articole`, interogat prin
|
||||
`cauta_nom_articole` (`COMUN\programe\ocautare.prg:1781-1823`, `SELECT ... FROM ] + gcS + [.vnom_articole_toate a`
|
||||
la `:1799-1803`). **Acesta e cel folosit de `frm_incasare_finala.do_adauga`** — fara pret
|
||||
atasat automat (pretul se tasteaza manual, vezi punctul 1).
|
||||
- **Motorul de preturi negociate/politici de pret**: `pack_facturare.cursor_preturi`, apelat din
|
||||
`COMUN\programe\ofacturare.prg` in `factureaza`/`factureaza2` (liniile 276/281/464/755/760) —
|
||||
parte a mecanismului generic COMUN de facturare/avizare pe baza de "lista de preturi"
|
||||
(`factureaza(22)`, `factureaza(29)`, `factureaza(23)`, `factureaza(41)` — comentate explicit
|
||||
`pe baza de lista de preturi` in `COMUN\programe\oproceduri_facturare.prg:205,237,268`).
|
||||
**Nu am gasit niciun apel** al acestui `factureaza`/`factureaza2` generic in fisierele
|
||||
specifice ROAAUTO (`oviz_devize.vc2`, `oproceduri_devize.prg`) — grep-urile pentru
|
||||
`cursor_preturi` si pentru `factureaza(` in afara de `COMUN\programe\ofacturare.prg` nu au
|
||||
gasit potriviri in cod ROAAUTO. **Concluzie**: fluxul propriu ROAAUTO de facturare deviz
|
||||
(`factureaza_deviz`) foloseste exclusiv nomenclatorul brut, fara calcul automat de pret
|
||||
negociat; motorul `cursor_preturi` pare sa apartina unui flux de facturare/avizare generic,
|
||||
separat, neapelat din codul ROAAUTO analizat.
|
||||
|
||||
## Neverificat / limitari
|
||||
|
||||
- Wiring-ul meniu -> `frm_facturi` in ROAAUTO (punctul 5).
|
||||
- Efectul `sterge_factura` asupra coloanelor `nrfact`/`facturat` pe comanda ROAAUTO (necesita
|
||||
`pack_auto`, neinclus in scriptul Oracle analizat).
|
||||
- Numele exact al butonului/caption care declanseaza `frm_incasare_finala.do_adauga` — nu am
|
||||
gasit in `oviz_devize.vc2` un `Click` explicit legat de `do_adauga`; e foarte probabil declansat
|
||||
de un buton generic `cmd_adauga` prin conventia clasei de baza `_frm_base` (folosita si de alte
|
||||
metode `do_xxx`/`cmd_xxx` din acelasi fisier), dar nu am gasit dovada directa a legaturii.
|
||||
- Indexul ROAAUTO a fost reconstruit local (`_symbols.tsv`, permis explicit); nu a fost rulat
|
||||
`git_sync.ps1` si nu s-a generat text nou din binare in ROAAUTO.
|
||||
|
||||
## Nota
|
||||
|
||||
Raportul a fost scris initial (din greseala) la calea gresita `D:\ROA\ROAAUTO\docs\cercetare\...`
|
||||
in loc de `D:\ROA\ROAFACTURARE\docs\cercetare\...`. Acest fisier e livrarea corecta, la calea
|
||||
ceruta.
|
||||
167
docs/cercetare/roaauto_facturi.md
Normal file
167
docs/cercetare/roaauto_facturi.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Cercetare: facturile emise din ROAAUTO si relatia lor cu VANZARI_DETALII
|
||||
|
||||
Scop: pentru `plan_13_unificare_formular_facturare.md` — poate formularul unificat sa acopere si
|
||||
facturile emise din ROAAUTO (piese + manopera pe deviz), stiind ca ele "se pot modifica ulterior"?
|
||||
|
||||
## Metoda si ce am putut verifica
|
||||
|
||||
ROAAUTO (`D:\ROA\ROAAUTO`) e un working copy **migrat** (are deja `.vc2`/`.sc2` in-tree, ca
|
||||
ROAFACTURARE), deci am cautat direct in text, **fara sa rulez `git_sync.ps1`** (interzis explicit).
|
||||
Nu pot garanta ca acel text e sincron 100% cu binarul curent (nu l-am regenerat) — daca vreo linie
|
||||
citata pare sa nu corespunda comportamentului live, motivul cel mai probabil e text neactualizat, nu
|
||||
o citire gresita.
|
||||
|
||||
`D:\ROA\_vfp_textcache\roaauto\_symbols.tsv` exista dar indexeaza **doar `.prg`** (1924 intrari,
|
||||
toate `.prg`; niciun `.vc2/.sc2`) — probabil generat inainte de migrarea in-tree. Nu m-am bazat pe
|
||||
el; am cautat direct cu Grep in `.vc2`/`.prg` din arbore.
|
||||
|
||||
Pentru partea Oracle am gasit sursa curenta a pachetului `PACK_FACTURARE` (COMUN, shared) in
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` (17020 linii,
|
||||
cel mai recent script din istoricul de migrari) — folosita ca sursa de adevar pentru semnaturile
|
||||
procedurilor.
|
||||
|
||||
## 1. Formularul/programul care emite facturi in ROAAUTO
|
||||
|
||||
Nu e un formular separat de facturare, ci un **modul apelat din formularul de devize/comenzi**
|
||||
(`frm_...` in `oviz_devize.vc2`, clasa cu `cmd_factavans`/`cmd_factfinal`). Logica de facturare
|
||||
propriu-zisa e in `Programe/oproceduri_devize.prg`:
|
||||
|
||||
- `Procedure factureaza_deviz` — `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:846` (semnatura la
|
||||
linia 847: `Lparameters tnIdComanda,tcNrOrd,tcNrInmat,tcDenop,tnMultiple,...`).
|
||||
- Apelata din formular in `D:\ROA\ROAAUTO\Clase\oviz_devize.vc2` (liniile 1834, 2201, 3146, 4056,
|
||||
4456) prin butoanele de facturare avans/final.
|
||||
- Exista si `Procedure relisteaza_factura_deviz` —
|
||||
`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1516` — pentru **re-listarea** (re-tiparirea) unei
|
||||
facturi deja emise, nu pentru editarea ei.
|
||||
|
||||
## 2. Cum scrie in Oracle: aceleasi proceduri, cale comuna cu ROAFACTURARE
|
||||
|
||||
Da — **acelasi pachet Oracle `PACK_FACTURARE`** (COMUN, shared cu ROAFACTURARE), apelat prin
|
||||
`goExecutor.oExecute`, cu doi apeluri specifice pe langa cele generice:
|
||||
|
||||
- `pack_facturare.initializeaza_date_factura(...)` —
|
||||
`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1211`
|
||||
- `pack_facturare.adauga_articol_factura_deviz(...)` (varianta **_deviz** a
|
||||
`adauga_articol_factura`, cu parametri expliciti de pret/gestiune/valuta in loc sa caute articolul
|
||||
din nomenclator) — `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1240`, in bucla `Scan`/`Endscan`
|
||||
peste cursorul `crsvanztemp`.
|
||||
- `oscrie_in_fisiere(0,.F.,.T.)` — `D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:1269`.
|
||||
- `pack_facturare.scrie_incasari(...)` — linia 1294 (doar daca exista incasare la emitere).
|
||||
- `pack_facturare.scrie_in_vanzari(0, id_delegat, id_masina, id_facturare, ..., @poDate.nid_vanzare)`
|
||||
— linia 1302, **urmata in acelasi `lcSql`** de `pack_auto.actualizeaza_deviz(...)` (linia 1310) —
|
||||
un apel specific ROAAUTO, care actualizeaza starea devizului/comenzii dupa facturare (marcheaza
|
||||
`RUL`, seteaza `nrfact` etc., nu am citit corpul lui `pack_auto` — pachet separat, nu l-am cautat).
|
||||
- Corpul `adauga_articol_factura_deviz` (spec+body in `ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:468`
|
||||
si `:4675-4745`) face un simplu `INSERT INTO VANZARI_DETALII_TEMP (...)` — **exact tabela de
|
||||
staging** pe care o foloseste si facturarea normala din ROAFACTURARE (`adauga_articol_factura`,
|
||||
linia 549 din spec, insereaza in acelasi `VANZARI_DETALII_TEMP`).
|
||||
- **Premisa lui Marius e confirmata, cu o nuanta**: ROAAUTO nu scrie direct in `VANZARI_DETALII`; ca
|
||||
si fluxul normal, trece prin `VANZARI_DETALII_TEMP`, iar transferul definitiv catre
|
||||
`VANZARI`/`VANZARI_DETALII` se face in `pack_facturare.scrie_in_vanzari`
|
||||
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:915-923` spec, body la `:13497-13962`) — **acelasi
|
||||
punct final** pe care il foloseste orice alta facturare ROA (avize, comenzi, contracte). Nu exista
|
||||
cale Oracle proprie ROAAUTO pentru scrierea liniilor de factura.
|
||||
|
||||
## 3. Tipul de document: `tip = -12`
|
||||
|
||||
`factureaza_deviz` construieste obiectul de date cu
|
||||
`poDate = Createobject("oDateFactura",lnIdSet,-12)` —
|
||||
`D:\ROA\ROAAUTO\Programe\oproceduri_devize.prg:922` — al doilea parametru e `tip`. Clasa
|
||||
`oDateFactura` nu am gasit-o definita ca text (nu apare `DEFINE CLASS oDateFactura` in nicio sursa
|
||||
text din ROAAUTO sau din COMUN al oricarui proiect cautat) — probabil traieste intr-un `.vcx` inca
|
||||
neconvertit sau e generata dinamic; **neverificat** unde anume e clasa, dar e clar shared (folosita
|
||||
si in `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare_comun.vc2`, ex. liniile 3894/4075/7090, cu acelasi
|
||||
tipar `Createobject("oDateFactura", tip1, tip2)`).
|
||||
|
||||
**`tip = -12` e deja cunoscut in ROAFACTURARE** — nu ca un cod nou, ci ca ceva deja intalnit si
|
||||
documentat in cercetarea recenta de regresie a editarii facturii (proiectul S4,
|
||||
`docs\progres.md:339`, `:1142`; `docs\cercetare\rec_datoria6_baza_regresie.md:93`;
|
||||
`docs\cercetare\rec_s4_runda1.md:38`; `COMUN\docs\cercetare\rec_view_articole_vanzare.md:108`), pe un rand
|
||||
real din baza de test `MARIUSM_AUTO`: `cod=1140885` -> `id_vanzare=1047`, `tip=-12`. Acolo e descris
|
||||
explicit: **"nu e factura, e alt tip de document"** (handoff intermediar (sters)) si decizia produsului
|
||||
(decizia 19) e ca detectia liniilor de articole "merge pe orice tip, nu doar facturi" — adica
|
||||
`IncarcaArticoleFactura`/`IncarcaVanzareNota` din `COMUN\programe\ofacturare_editare.prg` (mentionate
|
||||
in `rec_datoria6_baza_regresie.md:83-84`) nu filtreaza dupa `tip=1`, deci **vad si randurile
|
||||
`tip=-12`** deja, fara cod suplimentar.
|
||||
|
||||
## 4. Ce e specific fata de o factura obisnuita
|
||||
|
||||
- **Camp de legatura cu masina**: `V_ID_MASINA` e transmis catre `pack_facturare.scrie_in_vanzari`
|
||||
(`oproceduri_devize.prg:1304`). Nu e un camp exclusiv ROAAUTO — `ID_MASINA` e coloana standard pe
|
||||
`VANZARI`, cunoscuta si de fluxurile generice din pachet: `modifica_date_factura` are parametrul
|
||||
`V_ID_MASINA IN NUMBER` (`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:946`), la fel
|
||||
`scrie_factura2`, `scrie_factura_avize`, `scrie_factura_avize_retur` (liniile 618-747 din acelasi
|
||||
fisier). Deci nu e o bariera de schema — ROAFACTURARE stie deja de `ID_MASINA`.
|
||||
- **Liniile facturii nu sunt articole individuale din nomenclator, ci sume cumulate cu
|
||||
pseudo-articole (id negative)**: `factureaza_deviz` agrega toate sumele din contul analitic al
|
||||
devizului (`actactan`) pe categorii si insereaza cate o linie sintetica per categorie —
|
||||
`-100000` MANOPERA (`oproceduri_devize.prg:965-967`), `-100001` DISCOUNT MANOPERA (`:972`),
|
||||
`-100003` MATERIALE (`:947-949`), `-100005` AVANS (`:936-937`), `-100006` STORNARE AVANS
|
||||
(`:1026-1027`), `-100007`/`-100008` INSPECTIE TEHNICA / SPALARE AUTO (`:979-982`). Optional, daca
|
||||
`gnAUTOIdArticolReparatii` e setat, toate liniile de mai sus se **cumuleaza intr-o singura linie**
|
||||
cu un articol real din nomenclator ("REPARATII AUTO", `:989-1004`). Confirmat pe date reale in
|
||||
`COMUN\docs\cercetare\rec_view_articole_vanzare.md:108-109`: `id_vanzare=1047` (`tip=-12`) are doar
|
||||
**2 randuri** in `VANZARI_DETALII`, cu `id_gestiune`/`nume_gestiune` NULL (linii nestocate,
|
||||
netinute in gestiune). Deci pe factura nu apar piesele individual — apar totaluri
|
||||
MATERIALE/MANOPERA (cu exceptia cazului cumulat, unde apare un singur articol generic).
|
||||
- **`pack_auto.actualizeaza_deviz(...)`** e chemat imediat dupa `scrie_in_vanzari`, in acelasi
|
||||
bloc SQL (`oproceduri_devize.prg:1310`) — leaga vanzarea nou creata inapoi de deviz/comanda
|
||||
(tabelele `DEV_*`/comenzi din ROAAUTO). Corpul lui `pack_auto` nu a fost citit (pachet separat,
|
||||
in afara ariei `PACK_FACTURARE` cautate) — **neverificat** ce tabele ROAAUTO scrie exact.
|
||||
- **`DEV_TIP_DEVIZ`** (garantie/postgarantie/regie, `oproceduri_devize.prg:1797`) e o clasificare
|
||||
**diferita**, a devizului insusi, nu are legatura cu `VANZARI.tip=-12`.
|
||||
|
||||
## 5. Cum se modifica azi o astfel de factura
|
||||
|
||||
- **Din ROAAUTO**: nu exista editare a liniilor dupa facturare. Formularul de devize/comanda
|
||||
(`oviz_devize.vc2`) **dezactiveaza explicit butonul de modificare** dupa ce comanda are numar de
|
||||
factura: `Thisform.cmd_modifica1.Enabled=Iif(Empty(nrfact),.T.,.F.)` —
|
||||
`D:\ROA\ROAAUTO\Clase\oviz_devize.vc2:4536`. Singura actiune disponibila post-facturare gasita in
|
||||
cod e re-listarea (`relisteaza_factura_deviz`, `oproceduri_devize.prg:1516`), care doar reciteste
|
||||
antetul/liniile din `fact_vfacturi`/`fact_vfacturi_detalii` pentru reprintare, fara sa scrie
|
||||
nimic. N-am gasit niciun apel din ROAAUTO catre `pack_facturare.modifica_date_factura` (grep fara
|
||||
rezultate in tot arborele ROAAUTO) — nici macar antetul (delegat/masina/text aditional) nu pare
|
||||
editabil din ROAAUTO dupa emitere. **Neverificat**: n-am acoperit tot `oviz_devize.vc2` (fisier de
|
||||
>8800 linii) linie cu linie, doar zonele gasite prin grep pe termenii relevanti — nu exclud un alt
|
||||
punct de editare cu alt nume de metoda.
|
||||
- **Din ROAFACTURARE**: exista deja, in lucru (proiectul S4), un editor de factura care
|
||||
**citeste** liniile oricarui `tip` (inclusiv `-12`) din `VANZARI_DETALII` — funcțiile
|
||||
`IncarcaVanzareNota`/`IncarcaArticoleFactura` in `COMUN\programe\ofacturare_editare.prg` (adaugate
|
||||
conform `docs\cercetare\handoff_s4_runda1.md:79-81`, verificate pe `cod=1140885` -> `id_vanzare=1047`,
|
||||
`tip=-12`, `docs\cercetare\rec_datoria6_baza_regresie.md:93`) si formularul `frm_modific2024`
|
||||
(`COMUN\clase\omodificari.vc2`), cu un grid nou `grdArticoleFactura` pe pagina 3 "Articole factura".
|
||||
**Insa acest grid e in prezent READ-ONLY**: "grid nou `grdArticoleFactura` (...), `ReadOnly` la
|
||||
nivel de grid **si** pe fiecare `Text1`" (`docs\cercetare\handoff_s4_runda1.md:80-82`) — deci azi
|
||||
se poate **vizualiza**, nu edita, de aici (nici adaugare, nici stergere de linii).
|
||||
|
||||
## 6. Se poate adauga azi o linie libera / din lista de preturi pe o astfel de factura?
|
||||
|
||||
Pe cod, **nu, din nicaieri, azi**:
|
||||
|
||||
- Din ROAAUTO: butonul de modificare a devizului e dezactivat dupa facturare
|
||||
(`oviz_devize.vc2:4536`), iar singura cale de scriere gasita (`factureaza_deviz`) ruleaza o
|
||||
singura data la emitere; nu exista un `adauga_articol_...` apelabil ulterior pe o vanzare deja
|
||||
scrisa. Structura insasi a liniilor (sume cumulate pe pseudo-articole negative, sectiunea 4) e
|
||||
diferita de o linie normala de factura cu articol real din lista de preturi — un eventual "adauga
|
||||
articol" ar trebui sa lucreze langa niste linii care nu reprezinta articole reale.
|
||||
- Din ROAFACTURARE: editorul nou (`frm_modific2024`) **vede** liniile (orice `tip`, deci si cele
|
||||
venite din ROAAUTO), dar gridul e read-only — nu exista azi cod de adaugare/scriere pe acest grid.
|
||||
Nu am gasit alt formular ROAFACTURARE (`frm_facturi` clasic) care sa editeze articolele unei
|
||||
vanzari cu `tip=-12` — cautarea `ROAAUTO` in `D:\ROA\ROAFACTURARE` (in afara de `COMUN`) nu da
|
||||
potriviri de cod, doar mentiuni in `docs\` (progres.md, cercetare); `Grep` pe `ROAAUTO` in
|
||||
`D:\ROA\COMUNROA` a expirat (arbore prea mare) si nu a fost reincercat — **neverificat** daca
|
||||
`COMUNROA` (copia partajata separata de `COMUN` din ROAAUTO/ROAFACTURARE) are vreo referinta
|
||||
directa la ROAAUTO.
|
||||
|
||||
## Concluzie pentru planul de unificare
|
||||
|
||||
Premisa lui Marius se confirma pe cod: facturile ROAAUTO ajung, prin acelasi `PACK_FACTURARE`
|
||||
shared si acelasi `VANZARI_DETALII_TEMP` -> `VANZARI_DETALII`, in aceeasi tabela pe care o citeste
|
||||
ROAFACTURARE — deci un formular unificat **le poate vedea** fara cod special de recunoastere a
|
||||
sursei (dovada: editorul S4 le vede deja, testat pe date reale). Ce lipseste nu e recunoasterea, ci
|
||||
**capacitatea de scriere**: azi nimic, in niciun produs, nu poate adauga o linie pe o vanzare
|
||||
`tip=-12` — gridul nou din ROAFACTURARE e deliberat read-only, iar ROAAUTO isi blocheaza propriul
|
||||
formular dupa facturare. Particularitatea reala de gestionat e ca liniile existente nu sunt articole
|
||||
individuale, ci sume cumulate MATERIALE/MANOPERA pe pseudo-articole cu `id_articol` negativ — orice
|
||||
UI care adauga articole din lista de preturi "langa" ele trebuie sa decida cum coexista cu randuri
|
||||
care nu au `id_gestiune`/nu corespund unui articol real.
|
||||
249
docs/cercetare/rute_scriere_antet.md
Normal file
249
docs/cercetare/rute_scriere_antet.md
Normal file
@@ -0,0 +1,249 @@
|
||||
# Rute de scriere pe antetul unui document DEJA EMIS (VANZARI/ACT/RUL) — Grupele A/B/C
|
||||
|
||||
Cercetare read-only, fara nicio modificare de cod. Sfera: `pack_facturare.modifica_date_factura`
|
||||
(14 campuri, deja documentat in `modifica_date_factura_parametri.md`) e confirmata ca **singura**
|
||||
cale directa pentru serie/numar/data act/data scadenta + ruta/delegat/masina/agent/dataora_exp/
|
||||
id_facturare/listare_detaliata/text_aditional/tip_saft/efactura. Intrebarea de aici: exista alte
|
||||
cai, pentru campurile din grupele A/B/C, in afara drumului de emitere initiala.
|
||||
|
||||
**Rezumat**
|
||||
|
||||
| Camp | Verdict | Nota |
|
||||
|---|---|---|
|
||||
| A. ID_VENCHELT | NU (cu o rezerva) | vezi pct. 1.4 — posibil editabil direct in grid, neverificat pana la capat |
|
||||
| A. ID_SECTIE | **DA** (nou, activ) | prin editare directa a notei contabile, pct. 1.3-1.4 |
|
||||
| A. ID_RESPONSABIL | **DA** (nou, activ) | idem |
|
||||
| A. ID_LUCRARE | **DA** (nou, activ) | idem |
|
||||
| B. ID_FDOC (tip document) | NU | cautat, negasit — pct. 2 |
|
||||
| B. ID_VALUTA | NU | cautat, negasit |
|
||||
| B. Zi curs | NU | cautat, negasit |
|
||||
| B. ID_CLIENT | NU | cautat, negasit |
|
||||
| B. sursa/"altele" | NU | cautat, negasit |
|
||||
| B. gestiune sursa | NU | cautat, negasit |
|
||||
| B. politica de preturi | NU | cautat, negasit |
|
||||
| C. opt_incasat/casa/serie chit/nr chit/incasat/POS | NU direct, PARTIAL indirect | scrise doar la emitere (pct. 3.1); posibila cale indirecta prin editarea notei contabile daca incasarea e pe acelasi `cod` (pct. 3.2, neconfirmat pana la capat) |
|
||||
|
||||
---
|
||||
|
||||
## 1. Grupul A — analiticele de antet (ID_VENCHELT, ID_SECTIE, ID_RESPONSABIL, ID_LUCRARE)
|
||||
|
||||
### 1.1 Unde traiesc si cum se scriu la emitere
|
||||
|
||||
Coloanele sunt pe `ACT` (`ACT.ID_VENCHELT%TYPE`, `ACT.ID_SECTIE%TYPE`, `ACT.ID_RESPONSABIL%TYPE`,
|
||||
declarate asa in pachetul Oracle), nu pe `VANZARI`. La emitere, `frm_date_factura`/`frm_date_aviz`
|
||||
(`COMUN\clase\ofacturare.vc2:9041-9706` / `:6900-7516`, controale `Ct_clb_venchelt`/`Ct_clb_sectie`/
|
||||
`Ct_clb_responsabil`/`Ct_clb_lucrare`) populeaza variabilele de sesiune Oracle
|
||||
`pack_facturare.nid_venchelt` / `nid_sectie_stoc` / `nid_responsabil` / `nid_lucrare`
|
||||
(`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:1884-1887`), care se scriu o singura data, uniform pe
|
||||
tot documentul, in `INSERT INTO ACT_TEMP` la contabilizare (comentariul explicit de la linia 1286
|
||||
din `PACK_CONTAFIN.pck`: *"Am completat ID_RESPONSABIL la instructiunile INSERT INTO ACT_TEMP"*).
|
||||
Nicaieri in cele doua fisiere Oracle centrale nu exista `UPDATE ACT SET ID_VENCHELT|ID_SECTIE|
|
||||
ID_RESPONSABIL|ID_LUCRARE = ...` (grep combinat `UPDATE ACT ... SET` + fiecare coloana, zero
|
||||
potriviri, atat in pachetul curent `ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` cat si in
|
||||
`PACK_CONTAFIN.pck`, `PACK_UPDATE.pck`, `PACK_MIGRARE.pck` din `COMUN\docs`).
|
||||
|
||||
`frm_date_factura`/`frm_date_aviz` sunt instantiate **doar** din interiorul `ofacturare.vc2`
|
||||
insusi (nu am gasit niciun `Createobject` catre ele in afara clasei) — sunt exclusiv wizard-ul de
|
||||
la emitere, nu apar pe niciun flux de editare ulterioara.
|
||||
|
||||
**Concluzie partiala**: `modifica_date_factura` (calea documentata deja) NU atinge aceste 4
|
||||
campuri, si nu exista niciun `UPDATE` Oracle separat pe ele. **Pana aici, verdictul era NU.**
|
||||
|
||||
### 1.2 Descoperire care schimba raspunsul: `frm_facturi.do_editare_factura` (functionalitate noua)
|
||||
|
||||
Commit-ul cel mai recent din branch (`97d1613`, *"#6 editare factura emisa: omodificari.vcx intra
|
||||
in proiect"*) a adaugat exact ce lipsea. Exista deja o cercetare anterioara in acest depozit
|
||||
(`docs\cercetare\rec_modific2024.md`, scrisa inainte de acest commit) care documenteaza clasa
|
||||
`frm_modific2024` (`COMUN\clase\omodificari.vc2:6375+`, editor generic de nota contabila,
|
||||
partajat cu ROAGEST) si concluziona explicit: *"clasa exista deja, dar codul apelant din
|
||||
ROAFACTURARE NU exista inca — trebuie scris"*. **Acel gol a fost umplut** — am gasit apelantul,
|
||||
cablat si activ:
|
||||
|
||||
`COMUN\clase\ofacturare_comun.vc2`, metoda `frm_facturi.do_editare_factura` (~linia 3690-3869):
|
||||
|
||||
- Garda: blocheaza daca factura a fost trimisa in eFactura (`EsteInEFactura`, `:3764-3767`).
|
||||
- `IncarcaCursoareModificareNota(lnCod, pnAn, pnLuna, .F.)` (helper din `ofacturare_editare.prg`,
|
||||
citit dar nemodificat) incarca nota contabila completa (`vact_tot`/`vrul_tot`/`vrul_obinv_tot`
|
||||
filtrate pe `cod`) in cursoarele READWRITE `actactan`/`tact`/`rul_temp`/`trul`/
|
||||
`rul_temp_obinv`/`trul_obinv` (`:3769`).
|
||||
- `Omodif = Createobject([frm_modific2024], lnIdSet)` + `Omodif.Show()` (`:3796-3797`) — deschide
|
||||
editorul modal pe cursorul `tact` deja incarcat cu nota facturii curente.
|
||||
- Daca userul apasa Terminat (`buton = 1`, `:3799-3833`): deschide tranzactie manuala, sterge nota
|
||||
veche (`OSCRIE_IN_FISIERE(2,.T.,.T.)`), rescrie din cursoarele editate (`tact`->`actactan`,
|
||||
`trul`->`RUL_TEMP`, `trul_obinv`->`RUL_TEMP_OBINV`, cu `id_util`/`sters=0` noi),
|
||||
`OSCRIE_IN_FISIERE(0,.T.,.T.)` (scriere noua), apoi
|
||||
`pack_contafin.finalizeaza_modificare_nota(...)`, commit/rollback.
|
||||
|
||||
Acest `finalizeaza_modificare_nota` (`PACK_CONTAFIN.pck:8601-8651`, citat deja in
|
||||
`rec_modific2024.md`) **resincronizeaza `vanzari`** dupa editare: `SELECT COUNT(*) FROM vanzari
|
||||
WHERE cod = tnCod; IF > 0 THEN pack_facturare.actualizeaza_vanzari(tnCod, lnCodNou); END IF;`.
|
||||
|
||||
### 1.3 Ce e efectiv editabil in `tact`/`trul` — verificat prin `do_modifica`
|
||||
|
||||
`frm_modific2024.do_modifica` (`omodificari.vc2:13836-13957`) e mecanismul generic prin care un
|
||||
camp din grid deschide un dialog de cautare (`cauta_alfa`) si apoi face `REPLACE` pe cursorul
|
||||
corespunzator. Pentru `trul`/`trul_obinv` (nivel LINIE de nota, nu antet unic), `CASE` explicit
|
||||
gestioneaza:
|
||||
|
||||
```
|
||||
CASE m.lcControl = 'nrord' -> replace nrord with loCauta.nrord, id_lucrare with loCauta.id_lucrare
|
||||
CASE m.lcControl = 'sectie' -> replace sectie with loCauta.sectie, id_valuta with loCauta.id_sectie (!)
|
||||
CASE m.lcControl = 'nresp' -> replace nresp with loCauta.nume, id_responsabil with loCauta.id_responsabil
|
||||
```//omodificari.vc2:13934-13943
|
||||
|
||||
Deci **ID_LUCRARE si ID_RESPONSABIL sunt editabile explicit**, pe cursorul `trul` (nivel de linie
|
||||
RUL), prin acest mecanism, azi, din UI, prin `do_editare_factura`. Linia `sectie` are ce pare a fi
|
||||
un bug preexistent (`replace ... id_valuta with loCauta.id_sectie` in loc de `id_sectie` — cod
|
||||
existent, nu l-am atins, doar il semnalez ca observatie relevanta pentru evaluarea "functioneaza
|
||||
sau nu in practica"): daca bug-ul e real, campul afisat `sectie` (text) se schimba, dar coloana
|
||||
`id_sectie` propriu-zisa risca sa NU se actualizeze corect (se suprascrie `id_valuta` in loc).
|
||||
Nu am testat comportamentul, doar am citit codul.
|
||||
|
||||
**ID_VENCHELT**: NU exista un caz `CASE m.lcControl = 'venchelt'` (sau `dst_chlt`) in
|
||||
`do_modifica` pentru `trul`/`trul_obinv` — cautat explicit in tot procedeul (13836-13957), zero
|
||||
potriviri. Insa Grid1 al clasei (proprietatile de coloana, in afara metodelor) are o coloana cu
|
||||
`ControlSource = "dst_chlt"` (`omodificari.vc2:753, 2862, 7393` — a treia aparitie e in intervalul
|
||||
propriu al clasei `frm_modific2024`), deci explicatia venit/cheltuiala **e afisata** in grid. Daca
|
||||
acea coloana permite editare directa de text (nu prin popup de cautare) sau daca exista un alt
|
||||
handler (buton dedicat, dublu-click) care leaga `id_venchelt`, nu am verificat pana la capat — vezi
|
||||
"Necunoscute ramase". **Raspuns pentru ID_VENCHELT: NU confirmat ca ruta activa, dar cu o rezerva
|
||||
neinchisa** (grid-ul afiseaza coloana, mecanismul de editare exact al ei nu a fost trasat complet).
|
||||
|
||||
### 1.4 Nivel LINIE vs. nivel ANTET
|
||||
|
||||
O nuanta importanta: campurile din grupul A, la nivel Oracle, traiesc pe `ACT` (fiecare linie de
|
||||
nota isi are propriile `ID_VENCHELT`/`ID_SECTIE`/`ID_RESPONSABIL`/`ID_LUCRARE`), desi la EMITERE
|
||||
se scriu uniform (aceeasi valoare pe toate liniile unui document, din variabilele de sesiune).
|
||||
Editarea prin `frm_modific2024` e la nivel de LINIE (fiecare rand din `trul` se editeaza separat),
|
||||
nu o singura bifa de antet — deci userul poate, teoretic, sa lase valori diferite pe linii diferite
|
||||
ale aceleiasi facturi dupa editare, lucru care nu se putea intampla la emiterea initiala (uniforma).
|
||||
Asta conteaza pentru orice raportare care presupune "un singur ID_SECTIE per factura".
|
||||
|
||||
**Verdict grup A**: **DA** pentru `ID_SECTIE`/`ID_RESPONSABIL`/`ID_LUCRARE` (cale noua, activa,
|
||||
`frm_facturi.do_editare_factura` -> `frm_modific2024` -> `OSCRIE_IN_FISIERE` ->
|
||||
`pack_contafin.finalizeaza_modificare_nota` -> Oracle `ACT`/`RUL`, cu resincronizare in `VANZARI`).
|
||||
**NU confirmat** pentru `ID_VENCHELT` prin acelasi mecanism generic (`do_modifica` nu are caz
|
||||
pentru el), dar cu rezerva de la 1.3 neinchisa complet.
|
||||
|
||||
---
|
||||
|
||||
## 2. Grupul B — identitate/sursa (fdoc, valuta, zi curs, client, altele, gestiune sursa,
|
||||
politica preturi)
|
||||
|
||||
Controalele (`Ct_clb_fdoc`, `Ct_clb_valuta`, `Clb_zi_curs`, `Ct_clb_nume_client`, `Ct_clb_altele`,
|
||||
`Ct_clb_gestiune_init`, `Ct_clb_politici_preturi`) au fost gasite **exclusiv** in metodele proprii
|
||||
ale `frm_date_aviz`/`frm_date_factura`/`frm_date_aviz_lucrare` din `ofacturare.vc2` — acelasi
|
||||
wizard de emitere de la grupul A (cautare `vfp_symbols.ps1 -Grep` pe toate cele 7 nume de
|
||||
control, in tot proiectul indexat: 316 fisiere, zero potriviri in afara `ofacturare.vc2`).
|
||||
|
||||
Cautat in Oracle (pachetul curent + `PACK_CONTAFIN.pck` + `PACK_UPDATE.pck` + `PACK_MIGRARE.pck`):
|
||||
`UPDATE VANZARI ... SET (ID_FDOC|ID_VALUTA|ID_CLIENT|ID_PART|ID_GESTIN|ID_POL) = ...` si
|
||||
`UPDATE ACT ... SET` idem — zero potriviri (grep multiline, fereastra 400 caractere dupa
|
||||
`UPDATE`). Toate cele 13 aparitii ale `UPDATE VANZARI` din pachetul curent au fost inspectate
|
||||
individual (`sterge_factura`, `sterge_proforma`, `marcheaza_facturat`,
|
||||
`scrie_corespondente_vanzari`, plus `modifica_date_factura`) — niciuna nu atinge aceste coloane
|
||||
(ating `STERS`/`FACTURAT`/`ID_UTILFACT`/`DATA_FACTURAT`/`AVIZE`/`COD`/serie-numar-data-scadenta).
|
||||
|
||||
`frm_modific2024`/`do_editare_factura` (descoperirea de la grupul A) nu ajuta aici: cursoarele pe
|
||||
care le editeaza (`tact`/`trul`/`trul_obinv`) sunt nota contabila (conturi, sume, gestiuni de
|
||||
STOC, TVA) — nu contin fdoc/valuta document/client/politica de preturi ale facturii; acestea sunt
|
||||
proprietati ale `VANZARI`, populate o singura data la `scrie_factura2`/`scrie_in_vanzari`, in afara
|
||||
oricarui flux gasit de editare ulterioara.
|
||||
|
||||
**Verdict grup B: NU**, pentru toate cele 7 campuri — cautat si negasit, in ambele straturi
|
||||
(Oracle: pachetul de facturare curent + cele 3 pachete conexe; VFP: toate clasele indexate de
|
||||
`vfp_symbols.ps1`, 1128 clase / 9281 metode / 2461 proceduri).
|
||||
|
||||
---
|
||||
|
||||
## 3. Grupul C — incasarea
|
||||
|
||||
### 3.1 Unde se scrie la emitere
|
||||
|
||||
Controalele din `frm_alte_date` (`COMUN\clase\ferestre_cere_date.vc2:2219+`) — `opt_incasat`,
|
||||
`Cb_casa`, `Clb_serie_chit`, `Clb_nrchit`, `Clb_incasat`, `chkPOS` — au `ControlSource` pe
|
||||
proprietati ale obiectului `poDate` (`poDate.nIncasatPos`, si prin cod pe `poDate.incasat`,
|
||||
`poDate.nr_incasare`, `poDate.ntip_incasare`, `poDate.id_casa`, `poDate.serie_chit`), NU pe un
|
||||
cursor legat direct de tabel. `frm_alte_date` e instantiat exclusiv din
|
||||
`frm_facturare_articole(2).inainte_de_do_termin` si din doua fluxuri de listare
|
||||
(`ofacturare.prg:listare_protocol`, `ofacturare_stoc.prg:oscrie_vanzare_din_stoc`,
|
||||
`oproceduri_listari.prg:listare_protocol`) — toate parti ale fluxului de EMITERE, niciuna de
|
||||
editare ulterioara.
|
||||
|
||||
La `do_scrie_factura` (`ofacturare.vc2:14260-14389`, ambele forme `frm_facturare_articole` si
|
||||
`frm_facturare_articole2`), valorile din `poDate` sunt asamblate intr-un string
|
||||
`lcListaIncasare` (format `tip|suma|id_casa;...`) si trimise ca parametru la
|
||||
`pack_facturare.scrie_factura2` / `scrie_factura_avize` / `scrie_proforma` (apel RPC unic, la
|
||||
emitere). In Oracle, `scrie_factura2` cheama intern `pack_facturare.scrie_incasari` (linii 6204,
|
||||
7034 din pachetul curent), care parseaza lista si cheama `scrie_incasare2` per linie
|
||||
(`:13096-13168` -> `:13170+`) — aceasta insereaza o **nota contabila noua in `ACT_TEMP`**
|
||||
(coloane vazute: `ACT.SUMA`, `ACT.ID_PARTD` pentru casa/banca, `ACT.ASCC`/`SCD`/`ASCD`,
|
||||
`ACT_TEMP.PAYMENTCODE`), adica incasarea devine ea insasi o linie de jurnal (cont 5311/5121 etc.
|
||||
vs. 4111), scrisa prin acelasi flux `ACT_TEMP -> ACT` ca restul documentului, nu un camp separat
|
||||
pe `VANZARI`. `scrie_incasare2` nu are niciun apelant in afara lui `scrie_incasari` (cautat cu
|
||||
grep in tot pachetul de facturare — un singur call-site).
|
||||
|
||||
### 3.2 Cale de schimbare DUPA emitere
|
||||
|
||||
Nu exista nicio procedura Oracle de tip "modifica_incasare"/"corecteaza_incasare" (cautat
|
||||
`PROCEDURE ... (modifica|corecteaza|actualizeaza)...incasa...` in pachetul curent si
|
||||
`PACK_CONTAFIN.pck` — zero potriviri). `scrie_incasari`/`scrie_incasare2` nu au niciun apelant VFP
|
||||
direct (cautat in tot proiectul indexat — zero potriviri) — sunt folosite doar intern, o singura
|
||||
data, la emitere.
|
||||
|
||||
**PARTIAL, neconfirmat pana la capat**: incasarea scrisa la emitere e o linie de nota contabila
|
||||
(`ACT`/`RUL`) ca oricare alta. Daca acea linie foloseste acelasi `cod` (acelasi document contabil)
|
||||
ca restul facturii, atunci mecanismul nou de la Grupul A (`frm_facturi.do_editare_factura` ->
|
||||
`frm_modific2024`) ar incarca-o si pe ea in grid — si, editand campurile generice de nota
|
||||
(cont/gestiune/suma, prin acelasi `do_modifica` sau direct in grid), un utilizator ar putea
|
||||
schimba indirect suma/contul incasarii, deci si `casa`/`suma incasata` efectiva. **Nu am confirmat
|
||||
daca incasarea primeste acelasi `cod` sau un `cod` separat** — ar necesita fie testare live
|
||||
(interzisa aici, doar cercetare pe cod), fie citirea completa a insertiei din `scrie_incasare2`
|
||||
(am citit doar semnatura si primele ~35 linii, nu INSERT-ul propriu-zis in `ACT_TEMP`). Marchez
|
||||
explicit ca necunoscuta ramasa, nu ca "DA" confirmat.
|
||||
|
||||
`opt_incasat`/`chkPOS`/serie-numar chitanta/bon **ca atare** (proprietati `poDate` folosite doar
|
||||
pentru alocarea de numere de serie si generarea listei `lcListaIncasare`) nu au niciun camp
|
||||
persistent separat pe care sa-l poata atinge editarea ulterioara a notei — nu exista coloane
|
||||
`VANZARI.OPT_INCASAT`/`VANZARI.SERIE_CHIT` in tot ce am cautat.
|
||||
|
||||
**Verdict grup C**: NU pentru o cale directa/documentata de schimbare dupa emitere; PARTIAL,
|
||||
neconfirmat, prin editarea generica a notei contabile (acelasi mecanism nou de la Grupul A),
|
||||
DACA incasarea partajeaza `cod`-ul cu restul documentului.
|
||||
|
||||
---
|
||||
|
||||
## Ce am cautat (pentru un "NU" verificabil)
|
||||
|
||||
- **Oracle**: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql`
|
||||
(17020 linii, pachetul curent) — grep pe `UPDATE VANZARI`, `UPDATE ACT`, `UPDATE DOCUMENTE` (toate
|
||||
aparitiile citite in context), plus grep combinat `UPDATE <tabel> ... SET <coloana> =` (fereastra
|
||||
multiline 300-400 caractere) pentru fiecare coloana din grupele A/B/C. Idem pe
|
||||
`D:\ROA\ROAFACTURARE\COMUN\docs\PACK_CONTAFIN.pck` (9041 linii), `PACK_UPDATE.pck` (2420 linii),
|
||||
`PACK_MIGRARE.pck` (407 linii).
|
||||
- **VFP**: `vfp_symbols.ps1 -Grep -CodeOnly` (index complet: 316 fisiere, 1128 clase, 9281 metode,
|
||||
2461 proceduri) pentru fiecare nume de coloana Oracle si fiecare nume de control VFP din enunt
|
||||
(`Ct_clb_venchelt/sectie/responsabil/lucrare/fdoc/valuta/altele/gestiune_init/politici_preturi`,
|
||||
`Clb_zi_curs`, `opt_incasat`, `Cb_casa`, `Clb_serie_chit`, `Clb_nrchit`, `Clb_incasat`, `chkPOS`).
|
||||
Pentru fiecare formular gasit (`frm_date_factura`, `frm_date_aviz`, `frm_alte_date`), am cautat
|
||||
toti apelantii (`Createobject("...")`) in tot proiectul indexat, ca sa confirm ca sunt doar pe
|
||||
fluxul de emitere.
|
||||
- **Citire, fara scriere**: `ofacturare_comun.vc2` (metoda `do_editare_factura`, ~3690-3869) si
|
||||
`ofacturare_editare.prg` (integral, 457 linii) — apartin altui fir de lucru, doar citite.
|
||||
- `omodificari.vc2` (16464 linii) — clasa `frm_modific2024`: citite integral `do_modifica`
|
||||
(13836-13957) si `do_salvare` (13985-14042); NU am citit toate cele ~180 de metode ale clasei
|
||||
(Grid1.Column*, pgfArticole.*) — posibil sa existe alte cai de editare directa in grid,
|
||||
nedescoperite prin `do_modifica`.
|
||||
|
||||
## Necunoscute ramase
|
||||
|
||||
1. **ID_VENCHELT** (grup A): daca e editabil in grid-ul `frm_modific2024` prin alt mecanism decat
|
||||
`do_modifica` (coloana `dst_chlt` exista in grid) — neverificat pana la capat.
|
||||
2. **Incasarea pe acelasi `cod`** (grup C): daca linia de incasare scrisa de `scrie_incasare2`
|
||||
foloseste acelasi `cod` ca restul facturii (ceea ce ar face-o editabila prin
|
||||
`do_editare_factura`) — necesita citirea INSERT-ului efectiv in `ACT_TEMP` din
|
||||
`scrie_incasare2` (nu doar semnatura, citita aici doar partial) sau verificare pe date reale.
|
||||
3. **Bug-ul de la `sectie`** (`omodificari.vc2:13941`, `id_valuta with loCauta.id_sectie` in loc de
|
||||
`id_sectie`) — semnalat ca observatie, nu investigat mai departe (nu face parte din intrebare,
|
||||
dar afecteaza increderea in verdictul "DA" pentru `ID_SECTIE": campul e teoretic editabil, dar
|
||||
codul care il scrie pare sa aiba un bug care ar putea sa nu-l actualizeze corect in practica).
|
||||
468
docs/cercetare/s10_pret_contract_reemitere.md
Normal file
468
docs/cercetare/s10_pret_contract_reemitere.md
Normal file
@@ -0,0 +1,468 @@
|
||||
# S10 (runda 3) — Proiectare: pretul re-derivat din contract la reemiterea #13
|
||||
|
||||
Agent de proiectare (research), read-only. Nicio editare de cod, nicio scriere Oracle in afara de
|
||||
`SELECT`. Sursa SQL: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(17217 linii — **de data asta liniile citate coincid exact cu cele din raportul vechi
|
||||
`s10_pret_rederivat.md`, fara offset**). Date verificate pe `MARIUSM_AUTO@ROA_CENTRAL`, doar
|
||||
`SELECT`, 11.08.2026 — baza de dezvoltare, esantion mic (vezi sectiunea 2), nu productie.
|
||||
|
||||
## 0. Rezumat
|
||||
|
||||
**Intrebare adaugata de team-lead, raspuns scurt: NU.** `cursor_retur_document` (procedura folosita
|
||||
la INCARCAREA liniilor unui document existent, apelata din VFP cu `V_COPIERE=1`) **citeste pretul
|
||||
ca atare din `VANZARI_DETALII.PRET`, nu il re-deriva** — nu exista niciun `JOIN` catre
|
||||
`CTR_ARTICOLE`/`CRM_POLITICI_PRET_ART` in toata procedura (`:3949-4062`). Presupunerea din S8b se
|
||||
confirma pe cod. Detaliu complet, cu dovezi, in sectiunea 1bis (nou adaugata, tratata ca prioritara
|
||||
fata de restul raportului, conform cererii).
|
||||
|
||||
**Faptul portant (scrierea, nu citirea) se reconfirma integral** (`:5146-5185`) si se extinde cu
|
||||
doua descoperiri noi:
|
||||
|
||||
1. **TVA-ul si identitatea valutei sunt re-derivate din exact acelasi `JOIN`/`SELECT` ca pretul**,
|
||||
pe aceeasi ramura de contract — nu doar pretul e in pericol la reemitere, ci pretul + cota de
|
||||
TVA + valuta articolului, impreuna, tot-sau-nimic (sectiunea 3).
|
||||
2. **Nu exista nicio a doua re-derivare mai departe in lant** — `PRET` trece neschimbat de la
|
||||
`VANZARI_DETALII_TEMP` in `VANZARI_DETALII` (`scrie_in_vanzari`, `:13705-13757`, coloana `PRET`
|
||||
copiata direct, fara `DECODE`/recalcul) si `contabilizeaza_articol` nu scrie niciodata pe
|
||||
`VANZARI_DETALII.PRET` (scrie doar `ACT_TEMP`, nota contabila). Singurul loc de re-derivare e
|
||||
`adauga_articol_factura:5146-5185` (sectiunea 1) — **la scriere, niciodata la citire** (sectiunea
|
||||
1bis).
|
||||
|
||||
Pe date reale (esantion mic, baza de dezvoltare): **3 din 11 linii deja facturate pe contract au
|
||||
azi un pret de contract diferit de pretul efectiv facturat** — divergenta nu e ipotetica, e deja
|
||||
prezenta (sectiunea 2).
|
||||
|
||||
**Recomandare** (detaliata in sectiunea 4): varianta **(c) avertizare + confirmare explicita**,
|
||||
implementabila integral in VFP cu `goExecutor` (fara sa ating `pack_facturare`, conform deciziei
|
||||
27-bis), cu optiunea de a trece la **(b) blocare stricta** daca Marius prefera zero schimbari
|
||||
tacite vreodata. Varianta **(d) ocolire a ramurii** e posibila mecanic dar produce o regresie reala
|
||||
(pierderea legaturii `VANZARI_DETALII.ID_CTR`) — **nu o recomand**.
|
||||
|
||||
---
|
||||
|
||||
## 1. Reverificarea faptului portant
|
||||
|
||||
Cod citit direct din sursa (`:5039-5220`), nu preluat din raportul vechi.
|
||||
|
||||
```sql
|
||||
-- :5039-5050 — V_OPT_FACTURARE se calculeaza intern, din V_ID_CTR primit de la VFP
|
||||
IF pack_facturare.ntip IN (2, 6, 26, 52) THEN
|
||||
BEGIN
|
||||
SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
V_OPT_FACTURARE := 4;
|
||||
END;
|
||||
END IF;
|
||||
```
|
||||
|
||||
```sql
|
||||
-- :5146-5185 — ramura de contract, in interiorul CASE-ului principal (:5052-5220)
|
||||
WHEN V_OPT_FACTURARE = 3 THEN
|
||||
BEGIN
|
||||
SELECT DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR),
|
||||
B.PROC_TVAV,
|
||||
B.ID_VALUTA,
|
||||
A.PRET_CU_TVA,
|
||||
C.IN_STOC
|
||||
INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC
|
||||
FROM CTR_ARTICOLE A
|
||||
LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL_ART = B.ID_POL_ART
|
||||
LEFT JOIN NOM_ARTICOLE C ON B.ID_ARTICOL = C.ID_ARTICOL
|
||||
WHERE B.ID_ARTICOL = V_ID_ARTICOL AND A.ID_CTR = V_ID_CTR AND B.ID_POL = V_ID_POL;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
... V_PRET := V_PRET_TEMP; V_ID_VALUTA := V_ID_VALUTA_TEMP; V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP; V_IN_STOC := V_IN_STOC_TEMP;
|
||||
END;
|
||||
```
|
||||
|
||||
**Confirmat, cuvant cu cuvant fata de runda 9**: cu `OPT_FACTURARE = 3` (sau `NULL`, implicit 3),
|
||||
daca `CTR_ARTICOLE.PRET_UNITAR <> 0`, acea valoare **inlocuieste** `V_PRET_TEMP` (pretul trimis de
|
||||
VFP), necondiționat de faptul ca operatorul a atins sau nu linia. Procedura **nu are nicio notiune
|
||||
de reemitere** — parametrii sunt toti `IN` (`:4989-5059`, verificat), niciun canal care sa spuna
|
||||
"nu recalcula". `NO_DATA_FOUND` (politica/articol lipsa) e singurul caz care respecta pretul
|
||||
trimis.
|
||||
|
||||
### Nu exista alt loc care suprascrie pretul
|
||||
|
||||
- **`scrie_in_vanzari`** (`:13488+`), care muta liniile din `VANZARI_DETALII_TEMP` in
|
||||
`VANZARI_DETALII` (tabela finala) la finalizarea documentului: `PRET` e in lista de coloane
|
||||
copiate **neschimbat** (`:13705-13757`, `SELECT ... PRET ... FROM VANZARI_DETALII_TEMP` →
|
||||
`INSERT INTO VANZARI_DETALII (..., PRET, ...)`), fara `DECODE`/recalcul. Cautare exhaustiva pe
|
||||
fisier: **zero** `INSERT INTO VANZARI_DETALII` (tabela finala, nu `_TEMP`) in afara de acest
|
||||
punct.
|
||||
- **`contabilizeaza_articol`** (`:7173-7547`, reconfirmat structural fata de runda 9) citeste
|
||||
`detalii_articol.pret` (randul deja scris in `VANZARI_DETALII_TEMP`) doar ca sa calculeze suma
|
||||
notei contabile — nu scrie niciodata inapoi pe `VANZARI_DETALII.PRET`.
|
||||
- **`modificare_politica_stoc`** (`:2122-2135`) face un `UPDATE CRM_POLITICI_PRET_ART SET PRET=0,
|
||||
...` — e singurul alt `SET PRET` gasit in tot fisierul, dar reseteaza **politica** la schimbarea
|
||||
monedei stocului, complet in afara lantului de facturare a unei linii; nu atinge vreo linie de
|
||||
document.
|
||||
|
||||
**Concluzie sectiune**: raspunsul din runda 9 se confirma **integral**, fara nicio corectie, si se
|
||||
inchide explicit intrebarea "mai exista si alte locuri" — nu mai exista.
|
||||
|
||||
---
|
||||
|
||||
## 1bis. Citirea la incarcare: `cursor_retur_document` re-deriva pretul?
|
||||
|
||||
**Adaugat la cererea team-lead-ului — critic pentru S8b, tratat ca prioritar.**
|
||||
|
||||
### Verdict: NU. Pretul se citeste ca atare din `VANZARI_DETALII.PRET`, fara nicio re-derivare.
|
||||
|
||||
Corp complet, `PACK_FACTURARE:3949-4062`:
|
||||
|
||||
```sql
|
||||
PROCEDURE cursor_retur_document(V_IN_VALUTA IN NUMBER,
|
||||
V_LISTAID IN VARCHAR2,
|
||||
V_COPIERE IN NUMBER,
|
||||
V_PROFORMA IN NUMBER,
|
||||
V_ID_UTIL IN NUMBER,
|
||||
V_CURSOR OUT cursor_facturare) IS
|
||||
...
|
||||
BEGIN
|
||||
pack_facturare.initializeaza_facturare(V_ID_UTIL);
|
||||
|
||||
OPEN V_CURSOR FOR
|
||||
WITH CRS AS (SELECT X as ID_VANZARE FROM table(charn2collection(V_LISTAID, V_SEPARATOR)))
|
||||
SELECT ROWNUM AS ID_C, A.ID_ARTICOL, A.LOT, A.SERIE, A.ID_POL, A.ID_VALUTA,
|
||||
... DISCOUNT_UNITAR (derivat din A.DISCOUNT_UNITAR) ...,
|
||||
B.CODMAT, B.CODBARE, B.DENUMIRE, NVL(B.UM,'') AS UM,
|
||||
... GESTIONABIL ..., A.CANTITATE, A.PROC_TVAV, A.ID_JTVA_COLOANA,
|
||||
A.PRET_CU_TVA AS PRETURI_CU_TVA, A.CURS, A.MULTIPLICATOR,
|
||||
(CASE WHEN NVL(C.MONEDA_NATIONALA,1)<>1
|
||||
THEN ROUND(A.CURS * ROUND(A.PRET, pack_sesiune.nzecimale_pretvval) / NVL(A.MULTIPLICATOR,1), pack_sesiune.nzecimale_pretv)
|
||||
ELSE ROUND(A.PRET, pack_sesiune.nzecimale_pretv)
|
||||
END) + A.DIFERENTA AS PRET,
|
||||
...
|
||||
FROM (SELECT A1.ID_ARTICOL, A1.LOT, A1.SERIE, A1.ID_POL, A1.DISCOUNT_UNITAR, A1.DIFERENTA,
|
||||
A1.CANTITATE, A1.PROC_TVAV, A1.ID_JTVA_COLOANA,
|
||||
NVL2(A1.ID_GESTIUNE,1,0) AS GESTIONABIL, A1.PRET_CU_TVA, A1.PRET, A1.ID_VALUTA,
|
||||
NVL(A2.CURS,0) AS CURS, NVL(A2.MULTIPLICATOR,1) AS MULTIPLICATOR,
|
||||
A1.EXPLICATIE, A1.ID_GESTIUNE, A1.PRET_ACHIZITIE, A1.PRETD, A1.ID_JTVA_COLOANA_EX
|
||||
FROM VANZARI_DETALII A1
|
||||
LEFT JOIN VANZARI_CURSURI A2 ON A1.ID_VANZARE = A2.ID_VANZARE AND A1.ID_VALUTA = A2.ID_VALUTA
|
||||
WHERE A1.STERS = 0 AND A1.ID_VANZARE IN (SELECT ID_VANZARE FROM CRS)) A
|
||||
LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL
|
||||
LEFT JOIN NOM_VALUTE C ON A.ID_VALUTA = C.ID_VALUTA
|
||||
ORDER BY B.DENUMIRE;
|
||||
END cursor_retur_document;
|
||||
```
|
||||
|
||||
**Analiza surselor, coloana cu coloana:**
|
||||
|
||||
- **`A.PRET`** (`:4009-4015`, alias final `PRET`) vine din subquery-ul `A` care e
|
||||
**`VANZARI_DETALII A1`** direct (`A1.PRET`, `:4041`) — **nicio referinta la
|
||||
`CTR_ARTICOLE`/`CRM_POLITICI_PRET_ART`/`CRM_POLITICI_PRETURI` in toata procedura** (cautare
|
||||
exhaustiva pe corpul de la `:3949-4062`: zero hit-uri pentru oricare din cele trei tabele).
|
||||
Singurele `JOIN`-uri sunt:
|
||||
- **`VANZARI_CURSURI A2`** (`:4051-4053`), pe `ID_VANZARE`+`ID_VALUTA` — aduce `CURS`/
|
||||
`MULTIPLICATOR` **stocate pe documentul insusi** (cursul valutar de la momentul cand a fost
|
||||
scris, nu un curs "de azi" recalculat — tabela `VANZARI_CURSURI`, nu `CURS`).
|
||||
- **`NOM_ARTICOLE B`** (`:4056-4057`) — doar campuri descriptive (`CODMAT`, `CODBARE`,
|
||||
`DENUMIRE`, `UM`), acelasi tipar confirmat deja in `rec_pret_lazy.md` A2 pentru alte cursoare
|
||||
din pachet — niciodata sursa de pret.
|
||||
- **`NOM_VALUTE C`** (`:4058-4059`) — doar `MONEDA_NATIONALA`/`NUME_VAL`, folosit in `CASE` ca sa
|
||||
decida **cum se formateaza** afisarea (lei vs. valuta), nu ca sa aduca un pret alternativ.
|
||||
- Expresia finala pe `PRET` e o **transformare de afisare** a lui `A.PRET` (rotunjire +
|
||||
conversie in valuta folosind `A.CURS`/`A.MULTIPLICATOR` **stocate pe document**) plus
|
||||
`A.DIFERENTA` (coloana proprie pe `VANZARI_DETALII`, un ajustaj deja persistat pe linie, nu o
|
||||
recalculare live) — nu o re-derivare dintr-o sursa externa.
|
||||
- **Acelasi tipar pe `DISCOUNT_UNITAR`, `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`** — toate citite
|
||||
direct din `A1.*` (`VANZARI_DETALII`), fara vreun `JOIN` catre politica/contract.
|
||||
|
||||
**Confirmare suplimentara pe partea VFP — `cursor_retur_document` cu `V_COPIERE=1` e chiar calea
|
||||
folosita la reincarcarea unui document existent, cu prioritate fata de ramura de contract**:
|
||||
|
||||
```
|
||||
COMUN\programe\ofacturare.prg:266-283
|
||||
Do Case
|
||||
Case m.llCopiere
|
||||
lcSqlCursor = [{call pack_facturare.cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)}]
|
||||
Case Inlist(tnTip, 48, 49)
|
||||
...
|
||||
Case tnTip = 45
|
||||
...
|
||||
Case Inlist(tnTip, 1, 22, 5, 29, 7, 10, 23) && lista de preturi
|
||||
...
|
||||
Case Inlist(tnTip, 2, 26, 6, 52) && contract
|
||||
lcSqlCursor = [{call ]+gcS+[.pack_facturare.cursor_contract(...)}]
|
||||
```
|
||||
|
||||
`Do Case` in VFP executa **doar prima ramura adevarata**. Cand `llCopiere` e activ (incarcarea unui
|
||||
document existent — exact scenariul "editare prin regenerare" descris de team-lead), procedura
|
||||
apelata e **intotdeauna** `cursor_retur_document`, **indiferent de `tnTip`** — ramura de contract
|
||||
(`cursor_contract`, `:283+`) nu se mai ajunge deloc, chiar daca documentul e de tip 2/6/26/52.
|
||||
`cursor_retur` (`:3934-3947`) e doar un wrapper subtire peste `cursor_retur_document` cu
|
||||
`V_COPIERE=0`/`V_PROFORMA=0`, pentru returul propriu-zis — nu schimba concluzia.
|
||||
|
||||
### Consecinta pentru S8b
|
||||
|
||||
**Presupunerea "re-derivarea e o proprietate a scrierii, nu a citirii" se confirma pe cod, fara
|
||||
rezerve.** Deschiderea formularului de editare (etapa II, incarcarea liniilor existente) **nu**
|
||||
declanseaza nicio re-derivare de pret/TVA/valuta — documentul apare "neschimbat" la simpla
|
||||
deschidere, exact cum a presupus S8b. Mecanismul de detectare a schimbarii (compara starea
|
||||
incarcata cu starea la confirmare) **poate functiona ca premisa** — riscul de suprascriere tacita
|
||||
descris in restul acestui raport (sectiunile 1-6) apare **doar la re-scriere** (regenerare efectiva
|
||||
prin `adauga_articol_factura`), nu la incarcare. Cele doua momente (citire la deschidere, scriere
|
||||
la regenerare) sunt guvernate de **cursoare Oracle complet diferite** (`cursor_retur_document` vs.
|
||||
`adauga_articol_factura`), fara cod comun — o modificare facuta doar pe unul din ele (de exemplu o
|
||||
garda adaugata la scriere, variantele (b)/(c) din sectiunea 4) **nu afecteaza** comportamentul
|
||||
celuilalt.
|
||||
|
||||
---
|
||||
|
||||
## 2. Tipurile afectate si frecventa in date
|
||||
|
||||
**Tipurile de document**: `pack_facturare.ntip IN (2, 6, 26, 52)` (`:5039`), confirmat identic cu
|
||||
tabelul din `rec_editare_factura.md:176` (contract = tip 2/6/26/52, ramura de scriere
|
||||
`scrie_factura2`, la fel ca lista de preturi — nu are RPC separat).
|
||||
|
||||
**Frecventa in date** (schema `MARIUSM_AUTO`, doar `SELECT`, 11.08.2026):
|
||||
|
||||
```sql
|
||||
-- VANZARI_DETALII cu ID_CTR populat, active: 74
|
||||
-- din care, articolul are un rand corespunzator in CTR_ARTICOLE (id_ctr+id_articol): 11
|
||||
-- din acelea, CTR_ARTICOLE.PRET_UNITAR <> 0 SI diferit de VANZARI_DETALII.PRET: 3
|
||||
-- din acelea, CTR_ARTICOLE.PRET_UNITAR <> 0 SI egal cu VANZARI_DETALII.PRET: 8
|
||||
-- din acelea, CTR_ARTICOLE.PRET_UNITAR = 0 (nu suprascrie): 0
|
||||
```
|
||||
|
||||
**Interpretare, cu grija la marimea esantionului**: baza e de dezvoltare, cu doar 27 randuri totale
|
||||
in `CTR_ARTICOLE` — nu extrapolez procentul (27%) la volumul de productie. Dar **3 cazuri reale,
|
||||
nu zero**, e dovada suficienta ca fenomenul **chiar se intampla**, nu doar teoretic: daca oricare
|
||||
din cele 3 facturi ar fi reemisa azi prin regenerare (#13 etapa II), suma ei s-ar schimba tacit,
|
||||
fara nicio actiune a operatorului legata de pret. Simetric, cele 8 cazuri "egal" arata ca in
|
||||
majoritatea situatiilor curente reemiterea ar fi azi inofensiva — ceea ce face problema mai
|
||||
insidioasa, nu mai putin reala: cand se produce, nimeni nu se astepta, pentru ca de obicei nu se
|
||||
intampla nimic vizibil.
|
||||
|
||||
**Nu exista coloana de audit pe `CTR_ARTICOLE`** (confirmat din `user_tab_columns`: `ID_CTR_ART,
|
||||
ID_CTR, ID_POL_ART, PRET_UNITAR, CANT, COEF_DISCOUNT, VAL_DISCOUNT, ID_VALUTA, EXPLICATIE, UM,
|
||||
ID_ARTICOL, PRET_CU_TVA, ID_LOCATIA, PROC_TVAV, VALOARE` — nicio `DATA_MODIF`/`ID_UTIL_MODIF`) —
|
||||
nu exista cale de a masura *cat de des* se schimba `PRET_UNITAR` in timp, doar *cate cazuri
|
||||
divergente exista azi*. Raspunsul la "cat de des" ramane nedeterminabil din date; ce se poate
|
||||
spune e doar ca divergenta **exista deja**, azi, pe un esantion mic.
|
||||
|
||||
---
|
||||
|
||||
## 3. Descoperire noua: TVA si valuta sunt re-derivate din **aceeasi** interogare ca pretul
|
||||
|
||||
Din citatul de la sectiunea 1: `SELECT DECODE(A.PRET_UNITAR, ...), B.PROC_TVAV, B.ID_VALUTA, ...`
|
||||
— **un singur `SELECT`**, trei valori. Asta inseamna ca varianta de raspuns nu poate trata pretul
|
||||
izolat: daca politica de pret a contractului (`CRM_POLITICI_PRET_ART`, aceeasi `B`) si-a schimbat
|
||||
si cota de TVA sau valuta intre timp, reemiterea le suprascrie **pe amandoua**, prin acelasi
|
||||
mecanism, in aceeasi conditie (`NO_DATA_FOUND` → respecta ce a trimis VFP; altfel → suprascrie).
|
||||
|
||||
Comparativ cu discountul si cursul (reconfirmare fata de runda 9, sectiunile 6-7 ale raportului
|
||||
vechi, verificate din nou pe sursa curenta):
|
||||
|
||||
| Camp | Tratament pe ramura de contract | Risc la reemitere |
|
||||
|---|---|---|
|
||||
| **Pret** (`V_PRET`) | `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)` — suprascris daca `PRET_UNITAR<>0` | **Da — faptul portant** |
|
||||
| **TVA** (`V_PROC_TVAV`) | `B.PROC_TVAV`, din acelasi `SELECT`, fara fallback conditionat separat | **Da — aceeasi conditie ca pretul** |
|
||||
| **Valuta articol** (`V_ID_VALUTA`) | `B.ID_VALUTA`, idem | **Da — aceeasi conditie** |
|
||||
| **Discount** (`V_DISCOUNT_UNITAR`) | Parametru trimis de VFP, scris direct in `INSERT` (`:5266`), nicio ramura din `CASE` il citeste | **Nu — mereu respectat, indiferent de ramura** |
|
||||
| **Curs** (`V_CURS`) | `DECODE(V_CURS, 0, 1, V_CURS)` la `INSERT` (`:5270`) — inlocuieste doar 0 cu 1 | **Nu direct — dar vezi mai jos** |
|
||||
|
||||
**Consecinta combinata pret+valuta+curs**: daca `V_ID_VALUTA` re-derivat difera de valuta pentru
|
||||
care VFP a calculat `V_CURS` (de ex. politica a trecut de la EUR la USD intre emitere si
|
||||
reemitere), documentul reemis scrie **noua valuta cu vechiul curs trimis de VFP** — nicio
|
||||
validare incrucisata intre cele doua (reconfirmat, `:5270` nu verifica `V_ID_VALUTA`). Aceeasi
|
||||
observatie ca in runda 9, dar acum cu dovada ca sursa comuna (pret+TVA+valuta) inseamna ca **orice
|
||||
gardă construita pentru pret trebuie sa verifice si TVA si valuta in acelasi pas**, nu doar pretul
|
||||
— altfel o factura reemisa poate trece garda de pret nemodificata si totusi sa iasa cu alta cota
|
||||
de TVA sau alta valuta.
|
||||
|
||||
**Discountul nu ridica aceeasi problema** — e mereu respectat ca atare, indiferent de ramura,
|
||||
deci nu are nevoie de nicio garda la reemitere.
|
||||
|
||||
---
|
||||
|
||||
## 4. Variantele de raspuns
|
||||
|
||||
Toate patru presupun ca infrastructura de "sterge + reemite" din #13 etapa II trimite, pentru
|
||||
liniile de pe un document-contract, acelasi `V_ID_CTR` care era pe linia originala (asta e
|
||||
premisa explicita a sarcinii: "acelasi drum ca la emitere" — daca `V_ID_CTR` nu s-ar retrimite,
|
||||
linia nici n-ar mai fi "de pe contract").
|
||||
|
||||
### (a) Se accepta re-derivarea — nicio garda
|
||||
|
||||
**Ce se face**: nimic — regenerarea apeleaza `adauga_articol_factura` exact ca la emitere, cu
|
||||
acelasi `V_ID_CTR`. Comportamentul existent (sectiunea 1) se aplica neschimbat.
|
||||
|
||||
**Ce se strica**: criteriul din plan ("reemitere identica produce exact aceleasi sume" — vezi
|
||||
sectiunea 6) **esueaza silentios** pe orice linie de contract al carei pret/TVA/valuta a fost
|
||||
modificat intre timp. Dovedit sa se intample azi, pe date reale (sectiunea 2, 3 din 11). Utilizatorul
|
||||
care corecteaza o cantitate pe o factura veche poate vedea, fara sa i se spuna, totalul facturii
|
||||
schimbat pentru un articol la care nici nu s-a uitat.
|
||||
|
||||
**Cod atins**: zero. **Regresie pe emiterea normala**: zero (comportamentul de azi ramane
|
||||
identic).
|
||||
|
||||
### (b) Se blocheaza reemiterea cand pretul curent difera
|
||||
|
||||
**Ce se face**: inainte de a porni stergerea+regenerarea, VFP ruleaza un `SELECT` (prin
|
||||
`goExecutor`, exact tiparul deja folosit in `ofacturare.prg` pentru alte verificari punctuale —
|
||||
`goExecutor.oSelecteaza2Value(...)`, `:2628`, `:2661`) care compara, pentru fiecare linie a
|
||||
documentului cu `ID_CTR` populat, pretul/TVA/valuta stocate pe linie
|
||||
(`VANZARI_DETALII.PRET`/`PROC_TVAV`/`ID_VALUTA`) fata de ce ar recalcula azi ramura de contract
|
||||
(`CTR_ARTICOLE.PRET_UNITAR`/`CRM_POLITICI_PRET_ART.PROC_TVAV`/`.ID_VALUTA`, acelasi `JOIN` ca in
|
||||
sectiunea 1, dar rulat direct din VFP, fara sa ating `pack_facturare`). Daca gaseste macar o
|
||||
divergenta, **blocheaza regenerarea** cu mesaj ("Pretul de pe contract s-a schimbat pentru
|
||||
articolul X: <vechi> -> <nou>. Actualizati contractul sau anulati regenerarea.") si nu porneste
|
||||
deloc stergerea.
|
||||
|
||||
**Ce se strica**: orice regenerare pe un document cu **cel putin o linie** de contract cu pret
|
||||
schimbat e blocata **integral**, chiar daca utilizatorul voia sa corecteze cu totul altceva
|
||||
(ruta, o cantitate pe alta linie). Nu exista cale de a "accepta noul pret si continua" fara o a
|
||||
doua interactiune — varianta (c) rezolva exact asta.
|
||||
|
||||
**Cod atins**: doar VFP (o interogare noua, read-only, plus un dialog de blocare), inaintea
|
||||
apelului la infrastructura de stergere+regenerare din #13. **Zero atingere `pack_facturare`**
|
||||
(decizia 27-bis respectata). **Regresie pe emiterea normala**: zero — garda ruleaza doar pe
|
||||
calea de regenerare, nu la emiterea unui document nou.
|
||||
|
||||
### (c) Se avertizeaza si se cere confirmare, cu diferenta afisata
|
||||
|
||||
**Ce se face**: aceeasi interogare de detectie ca la (b), dar in loc sa blocheze, afiseaza un
|
||||
dialog cu lista liniilor divergente si delta (pret vechi vs. nou, si TVA/valuta daca difera —
|
||||
vezi sectiunea 3, toate trei trebuie aratate, nu doar pretul) si cere confirmare explicita
|
||||
("Continuati cu noile preturi de pe contract?" Da/Nu). La "Nu", regenerarea se opreste ca la (b).
|
||||
La "Da", regenerarea continua normal si ramura de contract face exact ce face azi (suprascrie).
|
||||
|
||||
**Ce se strica**: nu opreste suprascrierea insasi — doar o face **vizibila si asumata** inainte
|
||||
sa se produca, exact criteriul cerut de plan ("un rezultat explicit decis, nu accidental"). Nu
|
||||
ofera o cale de a **pastra** vechiul pret in timp ce se accepta restul modificarii — pentru asta
|
||||
ar trebui combinata cu (d), cu costul ei (vezi mai jos). Risc de "warning fatigue": daca
|
||||
divergenta e frecventa in productie (nu se poate masura azi, sectiunea 2), utilizatorii pot invata
|
||||
sa dea click pe "Da" fara sa citeasca.
|
||||
|
||||
**Cod atins**: identic cu (b) plus un dialog cu doua butoane in loc de unul singur. **Regresie pe
|
||||
emiterea normala**: zero.
|
||||
|
||||
### (d) Se ocoleste ramura — reemiterea trimite pretul documentului pe o cale care nu trece prin `DECODE`
|
||||
|
||||
**Posibil mecanic, cu un cost real.** Ramura de contract se selecteaza din doua conditii, ambele
|
||||
in afara controlului direct al apelantului per-linie:
|
||||
|
||||
1. `pack_facturare.ntip IN (2,6,26,52)` — variabila **de sesiune** (`:1882`,
|
||||
`pack_facturare.ntip := V_TIP`), setata o singura data la initializarea documentului (tipul
|
||||
documentului insusi), nu per linie. A o schimba ar insemna sa reclasifici tot documentul
|
||||
(afecteaza si alte ramuri `CASE` care depind de `ntip` — `:1905`, `:5053`, `:6056-6124`,
|
||||
`:14820` — cu efecte necunoscute si necontrolate) — **contrazice premisa "acelasi drum ca la
|
||||
emitere"** data explicit in sarcina. Nu recomand aceasta sub-varianta.
|
||||
2. `V_ID_CTR` — parametru **per linie**, trimis explicit de VFP la fiecare apel
|
||||
`adauga_articol_factura`. Daca VFP trimite `V_ID_CTR = NULL` pentru o linie la reemitere,
|
||||
blocul de la `:5039-5050` gaseste `NO_DATA_FOUND` (cautarea `WHERE ID_CTR = NULL` nu potriveste
|
||||
nimic) → `V_OPT_FACTURARE := 4` → ramura `WHEN V_OPT_FACTURARE = 3` nu se mai potriveste →
|
||||
cade pe `ELSE` (`:5187-5203`) → `V_PRET := V_PRET_TEMP` (pretul documentului, respectat ca
|
||||
atare, la fel TVA prin `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA`).
|
||||
|
||||
**Costul**: `V_ID_CTR` e si coloana scrisa in `VANZARI_DETALII_TEMP`/`VANZARI_DETALII.ID_CTR`
|
||||
(`:5280`, copiata neschimbat mai departe la `:13730`/`:13755`). Trimitand `NULL` la reemitere,
|
||||
linia **pierde definitiv legatura cu contractul** in tabela finala — orice raport care
|
||||
grupeaza/filtreaza pe `VANZARI_DETALII.ID_CTR` (regasire vanzari pe contract, situatii
|
||||
contractuale) nu va mai gasi acea linie dupa reemitere, desi articolul a fost, de fapt, livrat
|
||||
pe acel contract. E o regresie permanenta si greu de observat (nu da eroare, doar dispare
|
||||
tacit dintr-un raport), pe un camp folosit azi activ (`rec_pret_lazy.md`/`idpol_comanda_contract.md`
|
||||
confirma ca legatura cu contractul e cheie de raportare in tot lantul `PACK_FACTURARE`).
|
||||
In plus, o data pierduta legatura, **o a doua reemitere ulterioara nu ar mai putea re-deriva
|
||||
pretul din contract nici daca s-ar dori** — comutarea e ireversibila per linie, nu un flag
|
||||
comutabil.
|
||||
|
||||
**Nu recomand varianta (d)** — costul (pierderea trasabilitatii contract-linie, permanenta) e mai
|
||||
mare decat problema pe care o rezolva, si nici macar nu rezolva complet criteriul "reemitere
|
||||
identica" (rezolva doar pretul/TVA/valuta acelei linii, cu pretul unei regresii pe alt camp).
|
||||
|
||||
---
|
||||
|
||||
## 5. Discount, TVA, curs valutar — verificare fata de sectiunile 6-7 din raportul vechi
|
||||
|
||||
Deja tratat integral in sectiunea 3 de mai sus (descoperirea principala a acestei runde). Rezumat
|
||||
scurt pentru trasabilitate fata de cererea explicita:
|
||||
|
||||
- **Discountul** (sectiunea 6 raport vechi): reconfirmat — niciodata re-derivat, indiferent de
|
||||
ramura. **Nu ridica problema de reemitere.**
|
||||
- **TVA-ul** (sectiunea 6 raport vechi): reconfirmat ca "mereu recalculat, din surse diferite pe
|
||||
ramura" — dar cu precizarea noua ca pe ramura de contract, sursa **coincide exact** cu sursa
|
||||
pretului (acelasi `SELECT`, aceeasi conditie `NO_DATA_FOUND`). **Ridica aceeasi problema ca
|
||||
pretul, prin acelasi mecanism** — orice varianta (b)/(c) trebuie sa acopere si TVA, nu doar pret.
|
||||
- **Cursul valutar** (sectiunea 7 raport vechi): reconfirmat — `V_CURS` mereu respectat ca atare
|
||||
(`DECODE(V_CURS,0,1,V_CURS)`), nu se re-deriva direct. **Dar** identitatea valutei
|
||||
(`V_ID_VALUTA`) se re-deriva pe aceeasi ramura de contract, din acelasi `SELECT` — daca politica
|
||||
a schimbat valuta intre timp, reemiterea scrie noua valuta cu vechiul curs trimis de VFP, fara
|
||||
nicio validare incrucisata (reconfirmat, `:5270` nu verifica `V_ID_VALUTA`). **Ridica aceeasi
|
||||
problema, prin acelasi mecanism, si trebuie acoperita de aceeasi garda.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Cazul "reemitere identica"
|
||||
|
||||
Criteriul din plan (S12) cere ca o reemitere fara nicio modificare intentionata sa produca
|
||||
**exact** aceleasi sume. Verificat pe fiecare ramura a `CASE`-ului de la `:5052-5220` (nu doar
|
||||
ramura de contract):
|
||||
|
||||
| Ramura (`ntip`) | Garantat identic la reemitere? | De ce |
|
||||
|---|---|---|
|
||||
| `ELSE` (lista de preturi, fara contract) | **Da, cu o rezerva minora** | `V_PRET := V_PRET_TEMP` — passthrough direct (`:5200`). TVA vine din `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` (`:5188-5198`) — identic **doar daca** acea coloana de TVA nu a fost ea insasi editata intre timp (posibil, dar alt tip de risc, nu cel cerut de aceasta sarcina). |
|
||||
| `45` (restaurant) | **Da, cu aceeasi rezerva** | `V_PRET := V_PRET_TEMP` setat **inainte** de orice `SELECT` (`:5109`) — respectat necondiționat. TVA vine tot din `JTVA_COLOANE` (`:5114-5139`), aceeasi rezerva ca mai sus. |
|
||||
| `3,21,28,42,47` (comenzi) | **Nu garantat, dar esueaza tare, nu tacit** | `SELECT ... WHERE A.PRET = V_PRET_TEMP` (`:5077`) — daca pretul din comanda-sursa nu mai coincide exact cu ce trimite VFP, **`NO_DATA_FOUND` netratat** → eroare Oracle bruta, regenerarea pica integral, nu produce o suma gresita silentios. Diferit structural de ramura de contract. |
|
||||
| `4` (aviz) | **Nu garantat, silentios** | `SELECT DISTINCT A.PRET ... FROM VANZARI_DETALII` (`:5082-5103`), **fara filtru pe pret** — re-citeste pretul curent al liniei din avizul-sursa necondiționat. Daca avizul insusi a fost modificat intre emiterea initiala si reemitere, sau daca exista mai multe randuri candidate (`DISTINCT` pe o potrivire ambigua), reemiterea poate lua alt pret, tacit — **acelasi tipar de risc ca la contract**, dar pe alt document-sursa. |
|
||||
| `V_OPT_FACTURARE=3` (contract) | **Nu garantat, silentios** | Sectiunea 1 — faptul portant. |
|
||||
|
||||
**Observatie in afara perimetrului cerut, dar direct relevanta**: daca #13 etapa II ajunge sa
|
||||
regenereze si facturi emise initial din **aviz** (`ntip=4`), nu doar din contract, **acelasi tip
|
||||
de risc silentios exista si acolo**, prin alt mecanism (re-citire necondiționata din
|
||||
`VANZARI_DETALII` a avizului-sursa, fara filtru pe pret). Raportul vechi (`s10_pret_rederivat.md`,
|
||||
sectiunea 4) semnalase deja asta ca "neverificat, in afara bugetului" — de data asta o marchez
|
||||
explicit ca **decizie deschisa**: daca reemiterea din #13 se aplica si documentelor provenite din
|
||||
aviz, aceeasi discutie (a/b/c/d) trebuie purtata si pentru acea ramura, cu propriul cost (sursa
|
||||
de comparat e alt document, nu `CTR_ARTICOLE`). Nu am extins cercetarea de date pe aceasta ramura
|
||||
(in afara cererii explicite a sarcinii, care a cerut "cazul confirmat", adica cel de contract).
|
||||
|
||||
**Concluzie sectiune**: criteriul "reemitere identica" e garantat azi doar pe ramurile
|
||||
`ELSE`/restaurant (fara contract, fara comanda, fara aviz). Pe comenzi, o divergenta produce
|
||||
eroare vizibila (rau, dar nu tacit). Pe contract **si** pe aviz, o divergenta produce o suma
|
||||
gresita fara nicio semnalare — acestea sunt cele doua ramuri care au nevoie de o decizie explicita
|
||||
de tipul (a)-(d) inainte ca #13 etapa II sa poata pretinde ca satisface criteriul S12.
|
||||
|
||||
---
|
||||
|
||||
## 7. Ce ramane de decis de Marius
|
||||
|
||||
1. **(b) blocare stricta vs. (c) avertizare+confirmare** — (c) satisface literal criteriul "decis
|
||||
explicit, nu accidental" cerut de plan si nu intrerupe fluxul quasi-niciodata (doar cand chiar
|
||||
exista divergenta); (b) e mai sigur (niciodata nu se schimba nimic fara interventie manuala pe
|
||||
contract) dar mai frustrant daca divergentele sunt frecvente in productie — lucru nemasurabil
|
||||
azi (sectiunea 2). **Recomand (c)** ca implicit, cu mesajul aratand explicit delta de
|
||||
pret/TVA/valuta (sectiunea 3) — nu doar pretul.
|
||||
2. **Garda trebuie sa acopere pret + TVA + valuta impreuna**, nu doar pretul — confirmat la
|
||||
sectiunea 3 ca vin din acelasi `SELECT`. O implementare care verifica doar pretul ar lasa
|
||||
trecerea tacuta a unei schimbari de TVA sau valuta.
|
||||
3. **Varianta (d) (ocolire) — nu o recomand**, din cauza pierderii permanente a legaturii
|
||||
`VANZARI_DETALII.ID_CTR`. Daca Marius considera totusi ca merita costul (de ex. daca
|
||||
trasabilitatea contract-linie pe documentele reemise nu conteaza in practica), e o decizie de
|
||||
produs explicita, nu tehnica — semnalez aici, nu decid.
|
||||
4. **Ramura de aviz (`ntip=4`) are acelasi tip de risc silentios** (sectiunea 6) — de decis daca
|
||||
intra in perimetrul #13 etapa II acum sau se trateaza separat, cu propriul studiu de fezabilitate
|
||||
pentru garda (sursa de comparat difera fata de contract).
|
||||
5. **Frecventa reala in productie a divergentei `CTR_ARTICOLE.PRET_UNITAR`** ramane nemasurabila
|
||||
din baza de dezvoltare (27 randuri total) — daca decizia (b) vs (c) depinde de cat de des s-ar
|
||||
declansa garda, ar trebui repetata interogarea din sectiunea 2 pe o baza cu volum de productie
|
||||
inainte de implementare.
|
||||
|
||||
---
|
||||
|
||||
## STARE / CE RAMANE
|
||||
|
||||
**Livrabil complet** — toate cele 6 puncte cerute in brief sunt acoperite (sectiunile 1-6), plus
|
||||
recomandare argumentata (sectiunea 4, cu tabel comparativ) si lista de decizii deschise
|
||||
(sectiunea 7).
|
||||
|
||||
Nimic ramas in lucru; nicio verificare intrerupta. Singurele limitari explicite:
|
||||
- esantionul de date (27 randuri `CTR_ARTICOLE`, baza de dezvoltare) nu se extrapoleaza la
|
||||
productie (sectiunea 2);
|
||||
- ramura de aviz (`ntip=4`) e semnalata ca avand acelasi tip de risc, dar nu a primit acelasi nivel
|
||||
de cercetare (nu era in perimetrul cerut) — vezi sectiunea 6, ultimul paragraf inainte de
|
||||
concluzie.
|
||||
342
docs/cercetare/s10_pret_rederivat.md
Normal file
342
docs/cercetare/s10_pret_rederivat.md
Normal file
@@ -0,0 +1,342 @@
|
||||
# S10 — Pretul re-derivat in `adauga_articol_factura`, ramura cu ramura
|
||||
|
||||
## Nota pe sursa folosita
|
||||
|
||||
`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` **nu exista** (nici in `docs\`, nici urme in
|
||||
istoricul git — `docs\` e integral netracked). Fisierul cu **acelasi nume** exista insa la
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` si a fost folosit
|
||||
ca sursa. Continutul `adauga_articol_factura` coincide structural cu ce descrie planul (aceeasi
|
||||
ordine de ramuri, aceleasi coduri de eroare `FACT-012/013/018`, acelasi `DECODE(A.PRET_UNITAR, 0,
|
||||
V_PRET_TEMP, A.PRET_UNITAR)`), dar liniile sunt deplasate cu **+17** fata de citatele din plan
|
||||
(corp `4989-5284` aici vs. `4972-5268` in plan; ramura de contract `5146-5185` aici vs. `:5129-5168`
|
||||
in plan) — probabil diferente de comentarii de antet intre cele doua exporturi. Toate citatele de mai
|
||||
jos sunt pe fisierul din `SCRIPTURI_CLAR`, verificat direct.
|
||||
|
||||
## Verdict (raspuns la intrebarea 4 — reemiterea)
|
||||
|
||||
**Da, exista o ramura care suprascrie tacut pretul la orice apel, inclusiv la o eventuala
|
||||
reemitere: liniile provenite dintr-un contract cu `OPT_FACTURARE = 3` (sau `NULL`, care se
|
||||
implicit-eaza tot la 3).** Procedura nu are nicio notiune de "reemitere" — la fiecare apel cu acelasi
|
||||
`V_ID_CTR`, daca articolul are in `CTR_ARTICOLE.PRET_UNITAR` o valoare diferita de 0, acea valoare
|
||||
**inlocuieste** pretul trimis de VFP (`V_PRET_TEMP`), necontidionat de faptul ca linia a fost sau nu
|
||||
atinsa de utilizator pe ecran. Riscul se materializeaza doar daca pretul din contract s-a schimbat
|
||||
intre emiterea initiala si reemitere — daca n-a scazut, rezultatul e identic si nimeni nu observa
|
||||
diferenta pana cand se schimba. Pe toate celelalte ramuri (implicita, restaurant), pretul trimis de
|
||||
VFP e respectat ca atare. **Nu exista niciun parametru sau flag care sa opreasca re-derivarea** —
|
||||
raspunsul la intrebarea 5 e "nu exista".
|
||||
|
||||
## 1. Semnatura completa (corp, nu spec)
|
||||
|
||||
`PACK_FACTURARE:4989-5015` (spec identica la `:537-563`, verificat doar ca prezenta, nu citat):
|
||||
|
||||
```
|
||||
PROCEDURE adauga_articol_factura(V_ID_TEMP IN NUMBER,
|
||||
V_ID_ARTICOL IN NUMBER,
|
||||
V_SERIE IN VARCHAR2,
|
||||
V_EXPLICATIE IN VARCHAR2,
|
||||
V_ID_POL IN NUMBER,
|
||||
V_ID_GESTIUNE IN NUMBER,
|
||||
V_PRET_ACHIZITIE_TEMP IN NUMBER,
|
||||
V_PRETD IN NUMBER,
|
||||
V_ID_VALUTAD IN NUMBER,
|
||||
V_PRET_TEMP IN NUMBER,
|
||||
V_ID_VALUTA_TEMP IN NUMBER,
|
||||
V_PRETURI_CU_TVA_TEMP IN NUMBER,
|
||||
V_IN_STOC_TEMP IN NUMBER,
|
||||
V_CANTITATE IN NUMBER,
|
||||
V_DISCOUNT_UNITAR IN NUMBER,
|
||||
V_CONT IN VARCHAR2,
|
||||
V_CURS IN NUMBER,
|
||||
V_MULTIPLICATOR IN NUMBER,
|
||||
V_ID_JTVA_COLOANA IN NUMBER,
|
||||
V_ID_PART_REZ IN NUMBER,
|
||||
V_ID_LUCRARE_REZ IN NUMBER,
|
||||
V_PRETV_ORIG IN NUMBER,
|
||||
V_ID_VANZARE_SET IN NUMBER,
|
||||
V_ID_CTR IN NUMBER,
|
||||
V_ID_UTIL IN NUMBER,
|
||||
V_TAXCODE IN NUMBER DEFAULT NULL,
|
||||
V_LOT IN VARCHAR2 DEFAULT NULL) IS
|
||||
```
|
||||
|
||||
Toti parametrii sunt **IN**, niciunul OUT/IN OUT — nu exista un canal de intoarcere a pretului
|
||||
recalculat catre VFP la momentul apelului (linia se citeste ulterior, la re-interogarea grilei).
|
||||
|
||||
**`V_OPT_FACTURARE` nu e parametru.** E o variabila locala (`:5029`), calculata **in interiorul
|
||||
procedurii** din `CONTRACTE.OPT_FACTURARE`, folosind parametrul `V_ID_CTR` primit de la VFP
|
||||
(`:5039-5050`):
|
||||
|
||||
```sql
|
||||
IF pack_facturare.ntip IN (2, 6, 26, 52) THEN
|
||||
BEGIN
|
||||
SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
V_OPT_FACTURARE := 4;
|
||||
END;
|
||||
END IF;
|
||||
```
|
||||
|
||||
VFP nu trimite si nu poate influenta `V_OPT_FACTURARE` altfel decat prin `V_ID_CTR` (`poArt.id_ctr`,
|
||||
trimis `NULL` cand linia nu vine de pe contract). Pentru orice `pack_facturare.ntip` in afara lui
|
||||
`(2, 6, 26, 52)`, `V_OPT_FACTURARE` **ramane neinitializat** (blocul IF nu ruleaza deloc) — echivalent
|
||||
cu "nu conteaza", ramura de contract nu se poate nimeri.
|
||||
|
||||
## 2. Ce inseamna `V_OPT_FACTURARE` si cum se coreleaza cu VFP
|
||||
|
||||
Din `PACK_FACTURARE` (alte proceduri, aceeasi coloana `CONTRACTE.OPT_FACTURARE`) si din
|
||||
`COMUN\clase\ofacturare.vc2` / `COMUN\programe\oproceduri_facturare.prg` (proprietatea locala
|
||||
`poArt.opt_facturare`, populata la citirea contractului, in oglinda cu coloana Oracle):
|
||||
|
||||
| Valoare | Tip facturare | Dovada |
|
||||
|---|---|---|
|
||||
| `0` (sau lipsa contract) | Articol normal, nelegat de contract | `ofacturare.vc2:13055` `If poArticol.opt_facturare = 0` |
|
||||
| `1`, `2` | **Rata** (scadentar contract, `CTR_SCADENTAR`) — linia nu trece prin `adauga_articol_factura`, ci prin `adauga_rata_factura` (`ofacturare.vc2:14041-14050`) | `ofacturare.vc2:14041` `If Inlist(poArt.opt_facturare,1,2)`; `PACK_FACTURARE:2898,2915` (`CTR_SCADENTAR`, doar pt. `OPT_FACTURARE IN (1,2)`) |
|
||||
| `3` | **Articol de pe contract**, pret din `CTR_ARTICOLE` | `PACK_FACTURARE:2699,2820` (`CTR_ARTICOLE` doar pt. `OPT_FACTURARE = 3`); `ofacturare.vc2:14750` (`poDate.tip = 2 And poArticol.opt_facturare = 3`) |
|
||||
| `4` | Fallback intern cand contractul nu (mai) exista (`NO_DATA_FOUND`) | `PACK_FACTURARE:5047-5048` |
|
||||
|
||||
VFP nu trimite `V_OPT_FACTURARE` ca parametru al `adauga_articol_factura` — coreleaza doar indirect,
|
||||
prin faptul ca proprietatea locala `poArt.opt_facturare` (citita din acelasi `CONTRACTE.OPT_FACTURARE`
|
||||
cand grila s-a populat) decide **ce RPC se apeleaza** (`adauga_rata_factura` pentru 1/2,
|
||||
`adauga_articol_factura` pentru orice altceva, inclusiv 3), nu ce ramura Oracle se executa in interior
|
||||
— aceea o recalculeaza serverul singur, din `V_ID_CTR`.
|
||||
|
||||
## 3. Ramura cu ramura pe `V_OPT_FACTURARE` (si pe `pack_facturare.ntip`, care are prioritate)
|
||||
|
||||
`CASE` la `PACK_FACTURARE:5052-5220`. Ramurile 1-3 sunt selectate dupa `pack_facturare.ntip`, nu
|
||||
dupa `V_OPT_FACTURARE` — ramura 4 (contract) se testeaza **doar daca niciuna din primele trei nu s-a
|
||||
potrivit**.
|
||||
|
||||
| Selector | Tip facturare | Pretul | Sursa re-derivarii | Citat |
|
||||
|---|---|---|---|---|
|
||||
| `ntip IN (3,21,28,42,47)` | Facturare/aviz din **comenzi** | **Re-derivat, dar constrans sa se potriveasca cu ce a trimis VFP** — `SELECT A.PRET ... WHERE A.PRET = V_PRET_TEMP` | `COMENZI_ELEMENTE` + `CRM_POLITICI_PRETURI`/`PRET_ART`, filtrat pe `A.PRET = V_PRET_TEMP`; daca nu exista rand cu exact acel pret, `NO_DATA_FOUND` **nu e tratat** — procedura pica cu eroare Oracle netratata | `:5053-5078` |
|
||||
| `ntip = 4` | Facturare din **avize** | **Re-derivat necondiționat, din documentul sursa** — `SELECT DISTINCT A.PRET ... INTO V_PRET` fara filtru pe pretul trimis | `VANZARI_DETALII` (linia din avizul sursa), filtrata pe articol/politica/gestiune/discount/cont, dar **nu** pe pret | `:5080-5103` |
|
||||
| `ntip = 45` | Facturare **restaurant** | **Respectat** — `V_PRET := V_PRET_TEMP` setat **inainte** de SELECT (`:5109`); SELECT-ul recalculeaza doar `V_PROC_TVAV` si `V_PRET_ACHIZITIE` (cost, nu pret de vanzare) | n/a pentru pret; TVA din `JTVA_COLOANE`, cost din `CRM_POLITICI_PRET_ART.PRETFTVA` | `:5104-5145` |
|
||||
| `V_OPT_FACTURARE = 3` (deci `ntip IN (2,6,26,52)` **si** contract cu `OPT_FACTURARE=3`/`NULL`) | Facturare/aviz **de pe contract** | **Re-derivat daca contractul are pret setat** — `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)`; daca `PRET_UNITAR = 0` sau nu exista rand `CTR_ARTICOLE`/politica potrivita (`NO_DATA_FOUND`), cade pe `V_PRET_TEMP` | `CTR_ARTICOLE.PRET_UNITAR` (prin `CRM_POLITICI_PRET_ART`, `ID_POL_ART`) | `:5146-5185` (branch), `:5181-5184` (fallback `V_PRET_TEMP` in exceptie) |
|
||||
| `ELSE` (implicit — orice altceva, inclusiv linii normale fara contract) | Standard | **Respectat** — `V_PRET := V_PRET_TEMP` | n/a; TVA din `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` trimis de VFP | `:5187-5203` |
|
||||
|
||||
**Observatie de prioritate:** daca o linie e simultan "din comanda" (`ntip IN (3,21,28,42,47)`) si
|
||||
are si `V_ID_CTR` completat, ramura de comenzi castiga — `V_OPT_FACTURARE` nici nu se calculeaza
|
||||
(blocul de la `:5039` ruleaza doar pentru `ntip IN (2,6,26,52)`). Ramurile nu se pot suprapune in
|
||||
productie curenta, dar asta inseamna ca **orice extindere viitoare a lui `ntip`** (de ex. daca #13
|
||||
introduce un tip nou de document care refoloseste `adauga_articol_factura` pentru reemitere) trebuie
|
||||
verificata explicit fata de acest `CASE` — un `ntip` nou care nu intra in niciuna din listele
|
||||
`(3,21,28,42,47)` / `4` / `45` cade automat fie pe ramura de contract (daca are `V_ID_CTR`), fie pe
|
||||
`ELSE`.
|
||||
|
||||
## 4. Reemiterea — detaliu (vezi si verdictul de mai sus)
|
||||
|
||||
Procedura **nu primeste si nu poate primi** vreun semnal de tip "acesta e un apel de reemitere, nu
|
||||
recalcula". Comportamentul e identic la emiterea initiala si la orice apel ulterior cu aceiasi
|
||||
parametri. Riscul descris in plan (sectiunea H) se confirma integral pentru ramura de contract
|
||||
(`V_OPT_FACTURARE = 3`): daca `CTR_ARTICOLE.PRET_UNITAR` s-a modificat intre emiterea initiala si
|
||||
reemitere, factura reemisa va lua **pretul curent din contract**, nu pretul confirmat pe ecran de
|
||||
utilizator inainte de reemitere — chiar daca utilizatorul n-a atins linia.
|
||||
|
||||
Pentru ramurile "avize" (`ntip = 4`) si "comenzi" (`ntip IN (3,21,28,42,47)`), acelasi risc structural
|
||||
exista (pretul se re-citeste din documentul sursa la fiecare apel), dar **nu sunt cazul confirmat de
|
||||
#13** — planul S10 spune explicit "se verifica pe factura din contract, care e cazul confirmat" — nu
|
||||
s-a cerut si nu s-a facut inventar suplimentar pentru daca fluxul de reemitere din #13 (etapa II,
|
||||
stergere+regenerare cu aceleasi linii) ar trece vreodata prin aceste doua ramuri. **De retinut daca
|
||||
#13 extinde reemiterea si la facturi/avize provenite din comanda sau din alt aviz** — pe ambele,
|
||||
re-derivarea e chiar mai stricta decat pe contract (ramura de comenzi cade cu eroare netratata daca
|
||||
pretul nu se potriveste exact; ramura de avize suprascrie necondiționat).
|
||||
|
||||
## 5. Mecanism de "nu re-deriva"
|
||||
|
||||
**Nu exista.** Niciun parametru boolean, nicio valoare santinela, nicio ramura in `CASE`
|
||||
(`:5052-5220`) care sa respecte pretul primit *pentru ca i s-a cerut explicit*. Ramurile "implicita"
|
||||
si "restaurant" respecta pretul doar pentru ca structural nu au de unde re-deriva altceva (nu exista
|
||||
document sursa de recitit), nu pentru ca ar exista o comutare intentionata. Orice solutie de tip
|
||||
"pretul vine din formular, nu se re-deriva" (cum sugereaza planul, sectiunea H) **ar trebui
|
||||
implementata in VFP inainte de apel** (de exemplu, nu retrimite `V_ID_CTR` la reemitere daca vrei sa
|
||||
eviti ramura de contract) — nu exista un parametru in pachet pe care VFP l-ar putea seta ca sa
|
||||
dezactiveze re-derivarea, iar pachetul **nu se modifica** (decizia 27-bis, respectata — aceasta e o
|
||||
constatare, nu o propunere).
|
||||
|
||||
## 6. Discountul si TVA-ul
|
||||
|
||||
- **Discountul (`V_DISCOUNT_UNITAR`) nu e niciodata re-derivat.** Parametrul intra direct in
|
||||
`INSERT INTO VANZARI_DETALII_TEMP (..., DISCOUNT_UNITAR, ...) VALUES (..., V_DISCOUNT_UNITAR, ...)`
|
||||
(`:5236`, `:5266`) — nicio ramura din `CASE` il citeste sau il modifica. Tratament **diferit** de
|
||||
pret: discountul e mereu respectat, indiferent de ramura.
|
||||
- **TVA-ul (`V_PROC_TVAV`) e mereu recalculat server-side, pe toate ramurile — dar din surse
|
||||
diferite, nu dintr-un parametru brut.** Nu exista parametru `V_PROC_TVAV_TEMP` in semnatura; VFP
|
||||
trimite doar `V_ID_JTVA_COLOANA` (un identificator de coloana de cota, nu cota insasi). Pe ramura
|
||||
implicita si pe cea de contract-fara-potrivire, cota se cauta in `JTVA_COLOANE` dupa acel ID
|
||||
(`:5188-5198`, `:5169-5179`); pe ramurile comenzi/avize/contract-cu-potrivire/restaurant, cota vine
|
||||
din documentul sursa sau din politica de pret (`:5058`, `:5083`, `:5150`, `:5114`). Deci TVA-ul are
|
||||
un tratament **mai strict** decat pretul: niciodata "pass-through" direct, intotdeauna o cautare,
|
||||
doar sursa cautarii difera pe ramura.
|
||||
|
||||
## 7. Cursul valutar si pretul in valuta
|
||||
|
||||
- **Cursul (`V_CURS`) e mereu respectat ca valoare, pe toate ramurile.** Singura procesare e
|
||||
`DECODE(V_CURS, 0, 1, V_CURS)` la `INSERT` (`:5270`) — inlocuieste 0 cu 1, altfel scrie exact ce a
|
||||
trimis VFP. Procedura **nu interogheaza niciodata tabela `CURS`** pentru un curs "de azi" — nu are
|
||||
de unde sa recalculeze cursul chiar daca ar vrea.
|
||||
- **Identitatea valutei (`V_ID_VALUTA`, variabila locala, nu parametrul `V_ID_VALUTAD`) urmeaza
|
||||
acelasi tipar ca pretul, ramura cu ramura:** re-derivata din sursa pe comenzi (`C.ID_VALUTA`,
|
||||
`:5059`) si avize (`A.ID_VALUTA`, `:5084`), re-derivata din contract pe ramura `V_OPT_FACTURARE=3`
|
||||
(`B.ID_VALUTA`, `:5151`, cu fallback `V_ID_VALUTA_TEMP` in exceptie, `:5182`), respectata pe
|
||||
implicita si restaurant (`V_ID_VALUTA := V_ID_VALUTA_TEMP`, `:5110`, `:5201`).
|
||||
- **Consecinta pentru reemiterea unui document in valuta pe contract:** daca politica de pret a
|
||||
contractului (`CRM_POLITICI_PRET_ART`, prin `CTR_ARTICOLE.ID_POL_ART`) a fost schimbata intre timp
|
||||
la o alta valuta, reemiterea ar scrie articolul in **noua** valuta a politicii, dar cu **cursul**
|
||||
trimis de VFP (posibil cursul valutei vechi, daca VFP nu a fost actualizat sa retrimita cursul
|
||||
corect pentru noua valuta) — o sursa suplimentara de neconcordanta, nesemnalata explicit in plan.
|
||||
Nu s-a gasit nicio validare in procedura care sa verifice ca `V_CURS` corespunde valutei
|
||||
re-derivate `V_ID_VALUTA`.
|
||||
|
||||
## Completare: nota contabila a politicii
|
||||
|
||||
Sursa: aceeasi, `contabilizeaza_articol` la `PACK_FACTURARE:7173-7547` (in fisierul din
|
||||
`SCRIPTURI_CLAR`; corpul relevant pentru articol simplu — nu compus — e la `:7391-7544`), plus
|
||||
`scrie_nota` la `:12329-12561`.
|
||||
|
||||
### 1. Cum ajunge de la `id_pol` la nota contabila — ia toate randurile setului, fara distributie
|
||||
|
||||
`cursor_articol` (`:7218-7271`):
|
||||
|
||||
```sql
|
||||
FROM CRM_POLITICI_PRET_ART A
|
||||
LEFT JOIN CRM_POLITICI_PRETURI B ON A.ID_POL = B.ID_POL
|
||||
LEFT JOIN CRM_NOTE_VANZARI C ON B.ID_NOTA = C.ID_NOTA
|
||||
LEFT JOIN NOTE_CONTABILE D ON C.ID_SET = D.ID_SET
|
||||
WHERE A.ID_POL = detalii_articol.id_pol AND A.ID_ARTICOL = detalii_articol.id_articol
|
||||
```
|
||||
|
||||
E un `JOIN` simplu pe `ID_SET`, **fara `ROWNUM`, fara agregare, fara filtru suplimentar pe
|
||||
`NOTE_CONTABILE`**. Daca setul are `N` randuri, cursorul intoarce `N` randuri pentru aceeasi
|
||||
combinatie `id_pol`/`id_articol`. Bucla care il consuma (`:7393-7542`,
|
||||
`OPEN cursor_articol; FETCH ...; WHILE cursor_articol%FOUND LOOP ... FETCH ...; END LOOP;`) executa
|
||||
**intregul corp — `scrie_nota`, `descarca_gestiune`, `scrie_discount` — o data pentru fiecare rand
|
||||
din set**, cu **aceeasi cantitate si acelasi pret intreg de fiecare data**, nu impartite intre
|
||||
randuri. Nu exista nicio coloana `ORDINE` in tot pachetul (cautare `ORDINE` in fisier: zero
|
||||
rezultate) si nicio logica de distributie procentuala intre randurile unui set.
|
||||
|
||||
**Consecinta directa pentru reteta aprobata (J-quater):** daca politica tehnica pentru contul de
|
||||
venit ar ajunge sa foloseasca o nota al carei `ID_SET` are mai mult de un rand in
|
||||
`NOTE_CONTABILE` (cazul general — pana la 30 de randuri pe unele seturi din baza, per verificarea ta
|
||||
pe date vii), `contabilizeaza_articol` ar scrie **de N ori** aceeasi suma in contabilitate si ar
|
||||
apela `descarca_gestiune` de N ori pentru aceeasi linie de vanzare — dublare de venit si dublare de
|
||||
descarcare de gestiune, nu doar zgomot. Pasul 2 al retetei ("cauta o politica a carei nota are deja
|
||||
`SCC`-ul calculat") **trebuie sa garanteze un singur rand `NOTE_CONTABILE` per `ID_SET` ales**, nu
|
||||
doar un rand cu `SCC`-ul potrivit — verificarea facuta de tine pe cele 7 note active (exact un rand
|
||||
per set) e conditia care face reteta sigura azi, dar nu e impusa de cod, e o coincidenta a datelor
|
||||
curente.
|
||||
|
||||
### 2. `NOTE_CONTABILE.PTVA` — nu e citit deloc de `contabilizeaza_articol`
|
||||
|
||||
Lista de coloane a `cursor_articol` (`:7230-7260`) selecteaza explicit
|
||||
`ID_NOTA, PRETURI_CU_TVA, ID_VENCHELT, ID_SECTIE, ID_SET, EXPLICATIE, SCD, ASCD, SCC, ASCC, CU_TVA,
|
||||
IN_VALUTA` din lantul `CRM_POLITICI_PRET_ART -> CRM_POLITICI_PRETURI -> CRM_NOTE_VANZARI ->
|
||||
NOTE_CONTABILE` — **`D.PTVA` nu apare in acest `SELECT`**. Coloana exista pe tabel (confirmat de
|
||||
datele tale vii — `21`, `5`, `0`), dar `contabilizeaza_articol` n-o citeste niciodata.
|
||||
|
||||
Cota de TVA folosita efectiv in scriere e alta — vine din apelul catre `scrie_nota` (`:7444-7467`),
|
||||
unde parametrul `V_PTVA` primeste `detalii_articol.proc_tvav * 100 - 100` (`:7464`), adica **cota
|
||||
calculata deja in `adauga_articol_factura`** (din `JTVA_COLOANE` sau din documentul sursa, vezi
|
||||
sectiunea 3 de mai sus), nu din nota. Deci **raspunsul direct la intrebarea ta: daca articolul are
|
||||
21% dar nota politicii are `PTVA=5`, nu se intampla nimic — `PTVA` de pe nota e complet ignorat,
|
||||
cota folosita e cea a articolului/documentului, nu a notei.** Coloana `NOTE_CONTABILE.PTVA` e moarta
|
||||
din perspectiva acestei proceduri (posibil folosita in alt modul, neverificat aici).
|
||||
|
||||
### 3. `CU_TVA` si `IN_VALUTA` de pe nota
|
||||
|
||||
Ambele se citesc din `NOTE_CONTABILE` (`crs_rand_articol.cu_tva`, `crs_rand_articol.in_valuta`,
|
||||
`:7253`, `:7254-7260`) si se transmit mai departe la `scrie_nota` ca parametri (`V_CU_TVA`,
|
||||
`V_IN_VALUTA`), unde controleaza mecanic, nu semantic tot ce e legat de pret:
|
||||
|
||||
- **`V_CU_TVA`** (`scrie_nota:12416-12422`): daca `= 0`, suma acumulata in `V_INCASAT_CALCUL` e
|
||||
`V_SUMA_FARA_TVA`; daca `= 1`, e `V_SUMA_CU_TVA`. Separat, la `:12537-12558`, **doar cand
|
||||
`V_CU_TVA = 1` se scrie si o linie separata de TVA** (`pack_facturare.scrie_tva(...)`, cont
|
||||
`4427`/etc.). Deci `CU_TVA` decide daca acest rand din notă genereaza o inregistrare contabila
|
||||
separata pentru TVA sau nu — nu suprascrie si nu recalculeaza cota, doar comuta daca linia de TVA
|
||||
se scrie.
|
||||
- **`V_IN_VALUTA`** (`scrie_nota:12391-12404`, `:12492-12507`): daca `= 1`, se calculeaza si
|
||||
`V_SUMA_VAL`/`V_SUMA_FARA_TVA_VAL`/etc. (sumele in valuta straina) si randul `ACT_TEMP` scrie
|
||||
`ID_VALUTA = V_ID_VALUTA` (valuta reala a articolului) si `CURS = V_CURS`; daca `= 0`, randul
|
||||
scrie `ID_VALUTA = pack_facturare.nid_moneda_nationala` si `CURS = 0`, indiferent de valuta reala
|
||||
a articolului.
|
||||
**Raspuns la "ce se intampla daca `IN_VALUTA=1` pe nota dar documentul e in lei":** nu apare nicio
|
||||
eroare si nicio validare incrucisata intre `IN_VALUTA` de pe nota si valuta reala a documentului —
|
||||
procedura calculeaza pur si simplu `V_SUMA_VAL` folosind `V_ID_VALUTA` si `V_CURS` primite din
|
||||
`detalii_articol` (care, pe un document in lei, sunt deja moneda nationala si curs implicit 1).
|
||||
Rezultatul practic: `ACT_TEMP.SUMA_VAL` se populeaza redundant (cu aceeasi valoare ca `SUMA`,
|
||||
scalata la curs 1) in loc sa ramana `0`, iar `ID_VALUTA` de pe randul contabil ramane oricum
|
||||
moneda nationala (pentru ca asta e `detalii_articol.id_valuta` pe un document in lei) — o
|
||||
inconsistenta cosmetica in `ACT_TEMP` (rand cu `SUMA_VAL` populat desi `CURS` real e 1), nu o
|
||||
eroare de suma. Relevant pentru politicile `SERVICII VALUTA` / `COMISION INTERMEDIERE`
|
||||
(`SCC=704`, candidate la pasul 2 al retetei): daca oricare document in lei ar ajunge sa foloseasca
|
||||
una din ele, ar produce astfel de randuri cosmetic-inconsistente, fara sa strice suma facturata.
|
||||
|
||||
### 4. `ASCD`/`ASCC` si `ID_PARTD`/`ID_PARTC`
|
||||
|
||||
- **`ASCD`/`ASCC` nu sunt obligatorii — au fallback automat cand sunt nule**, exact pe ramura care
|
||||
se aplica pe facturi (`:7409-7428`):
|
||||
```
|
||||
V_ASCD := NVL(crs_rand_articol.ascd,
|
||||
PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD));
|
||||
...
|
||||
V_ASCC := NVL(crs_rand_articol.ascc,
|
||||
PACK_FACTURARE.GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCC));
|
||||
```
|
||||
Cand nota nu are analitic explicit (cazul majoritatii notelor reale, per verificarea ta), se
|
||||
deriva unul din grupul de utilizatori al celui care factureaza, functie de contul `SCD`/`SCC`. Nu
|
||||
s-a verificat in aceasta sesiune ce intoarce `GetAnaliticByGrupUtilizatori` cand nici grupul de
|
||||
utilizatori nu are un analitic configurat (posibil `NULL` mai departe, netratat explicit aici).
|
||||
- **`ID_PARTD`/`ID_PARTC` nu sunt citite de la nota deloc.** `cursor_articol` nu le selecteaza (nu
|
||||
exista `D.ID_PARTD`/`D.ID_PARTC` nicaieri in fisier — verificat). Cele doua coloane de pe
|
||||
`ACT_TEMP` se calculeaza in `scrie_nota`/`scrie_tva` direct din **codul contului** (`V_SCD`,
|
||||
`V_SCC`) si din variabile de sesiune ale pachetului (`pack_facturare.nid_part`,
|
||||
`nid_part_rez`, `nid_partc`), nu din nota: `ID_PARTD` e un `CASE` pe prefixul lui `V_SCD`
|
||||
(`41%`/`46%`/`45%`/`357`) la `:12510-12515`; `ID_PARTC` e un `DECODE` pe `V_SCC`
|
||||
(`419`/`4111`/`357`) la `:12523-12530`. Daca `NOTE_CONTABILE` are coloane `ID_PARTD`/`ID_PARTC`,
|
||||
ele sunt irelevante pentru `contabilizeaza_articol` — partenerul contabil vine intotdeauna din
|
||||
contextul documentului (`pack_facturare.nid_part`), nu din configurarea notei.
|
||||
|
||||
### 5. Politica fara `ID_NOTA` (`HOTEL TAXE`, `HOTEL CAZARE`)
|
||||
|
||||
**Nu exista un cod `FACT-0xx` dedicat pentru acest caz — spre deosebire de articolul care lipseste
|
||||
din politica (`FACT-024`, `:7278-7302`), aici procedura nu detecteaza si nu semnaleaza explicit
|
||||
situatia.** Lantul de `LEFT JOIN` din `cursor_articol` (`:7261-7271`) e construit sa supravietuiasca
|
||||
oricarui inel lipsa: `A` (politica-articol, gasita — altfel s-ar fi luat deja `FACT-024` mai
|
||||
devreme) se pastreaza chiar daca `B.ID_NOTA` e `NULL` — `C` si `D` devin pur si simplu toate
|
||||
`NULL` pe acel rand, cursorul tot intoarce **un rand** (`cursor_articol%FOUND = TRUE`), nu zero.
|
||||
|
||||
In bucla (`:7396-7541`), asta inseamna:
|
||||
- `crs_rand_articol.scd`, `.ascd`, `.scc`, `.ascc`, `.cu_tva`, `.in_valuta`, `.explicatie` — toate
|
||||
`NULL`.
|
||||
- Ramura de calcul `V_ASCD := NVL(NULL, GetAnaliticByGrupUtilizatori(nid_util, NULL))` — analitic
|
||||
calculat pe un cont `NULL`, deci probabil tot `NULL` (netestat aici ce intoarce functia pe
|
||||
argument `NULL`).
|
||||
- `scrie_nota` e apelata cu `V_SCD = NULL`, `V_SCC = NULL` — insereaza in `ACT_TEMP` un rand cu
|
||||
conturile de debit/credit **nule**.
|
||||
|
||||
Daca `ACT_TEMP.SCD`/`ACT_TEMP.SCC` au constrangere `NOT NULL` pe schema, `INSERT`-ul ar pica cu o
|
||||
eroare Oracle generica (`ORA-01400`), nu cu un mesaj `FACT-0xx` prietenos ca la celelalte cazuri
|
||||
tratate explicit — **neverificat in aceasta sesiune** (DDL-ul `ACT_TEMP` nu e in exportul
|
||||
`PACK_FACTURARE`). In orice caz, **comportamentul e cel putin la fel de riscant ca lipsa unui
|
||||
articol din politica**, dar fara plasa de siguranta explicita din cod — de tratat cu atentie daca
|
||||
reteta aprobata ajunge sa produca vreodata o politica fara `id_nota` (nu pare sa fie cazul retetei
|
||||
J-quater, care cere explicit gasirea unei note existente, dar `HOTEL TAXE`/`HOTEL CAZARE` arata ca
|
||||
starea "politica fara nota" chiar exista azi in baza vie, pe alte fluxuri).
|
||||
|
||||
## Ce nu s-a putut stabili si de ce
|
||||
|
||||
- **Comportamentul exact al viitorului flux de reemitere din #13** (etapa II) fata de aceasta
|
||||
procedura — planul S10 cere verificarea "pe factura din contract", nu inventarul complet pentru
|
||||
comenzi/avize; nu s-a extins cercetarea acolo (in afara observatiei de la punctul 4, care ramane o
|
||||
constatare structurala, nu un verdict testat pe fluxul de reemitere, care **inca nu exista in
|
||||
cod** — decizia 30, nimic implementat).
|
||||
- **Ce se intampla la eroarea netratata din ramura de comenzi** (`NO_DATA_FOUND` cand
|
||||
`A.PRET = V_PRET_TEMP` nu gaseste rand, `:5057-5078`) — daca propaga ca eroare Oracle bruta pana la
|
||||
utilizator sau daca `goExecutor` o intercepteaza generic; n-a fost verificat (nu era in perimetrul
|
||||
celor 7 intrebari, dar e un risc adiacent gasit din citirea codului).
|
||||
- **Cate contracte reale au azi `CTR_ARTICOLE.PRET_UNITAR` diferit de pretul ultimei facturi emise**
|
||||
— verificare pe date vii, nefacuta (cercetare read-only, fara acces la interogari live in aceasta
|
||||
sesiune).
|
||||
- **Discrepanta de numerotare fata de `docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`** (fisierul
|
||||
cerut in prompt nu exista pe disc) — semnalata la inceputul raportului, nu blocheaza verdictul
|
||||
pentru ca fisierul gasit in `SCRIPTURI_CLAR` are acelasi nume/data si continut structural identic.
|
||||
470
docs/cercetare/s10_rederivare_pe_calea_reemiterii.md
Normal file
470
docs/cercetare/s10_rederivare_pe_calea_reemiterii.md
Normal file
@@ -0,0 +1,470 @@
|
||||
# S10 — Se re-deriva valorile liniei pe calea de REEMITERE?
|
||||
|
||||
Stare: **TERMINAT.**
|
||||
|
||||
Surse:
|
||||
- `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17217 linii;
|
||||
`versiune_db.txt` = `2026_08_09_02`) — prescurtat **`PF:<linie>`**;
|
||||
- corpul viu din `all_source` (`MARIUSM_AUTO.PACK_FACTURARE`, `PACKAGE BODY`, **VALID**,
|
||||
`last_ddl_time = 2026-08-09 20:03:50`) — prescurtat **`LIVE:<linie>`**;
|
||||
- `COMUN\clase\ofacturare.vc2`, `COMUN\programe\ofacturare_editare.prg`.
|
||||
|
||||
**Exportul = ce ruleaza.** Verificat pe cinci ancore; in zona relevanta offsetul e constant,
|
||||
`LIVE + 1240 = PF`: `LIVE:3749`↔`PF:4989`, `LIVE:3840`↔`PF:5080`, `LIVE:3909`↔`PF:5149`,
|
||||
`LIVE:3982`↔`PF:5222`, `LIVE:4044`↔`PF:5284`, `LIVE:12466`↔`PF:13706`, `LIVE:13735`↔`PF:14975`.
|
||||
**Nu generalizez offsetul in afara zonei masurate** — il dau doar ca dovada de identitate.
|
||||
|
||||
---
|
||||
|
||||
## 1. Unde sta `SELECT`-ul de la `PACK_FACTURARE:5149-5166` si cine il cheama
|
||||
|
||||
**Numarul de linie e corect**, verificat direct pe fisier. `PF:5149-5166`:
|
||||
|
||||
```
|
||||
5149 SELECT DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR),
|
||||
5150 B.PROC_TVAV,
|
||||
5151 B.ID_VALUTA,
|
||||
5152 A.PRET_CU_TVA,
|
||||
5153 C.IN_STOC
|
||||
5154 INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC
|
||||
5159 FROM CTR_ARTICOLE A
|
||||
5160 LEFT JOIN CRM_POLITICI_PRET_ART B ON A.ID_POL_ART = B.ID_POL_ART
|
||||
5162 LEFT JOIN NOM_ARTICOLE C ON B.ID_ARTICOL = C.ID_ARTICOL
|
||||
5164 WHERE B.ID_ARTICOL = V_ID_ARTICOL AND A.ID_CTR = V_ID_CTR AND B.ID_POL = V_ID_POL;
|
||||
```
|
||||
|
||||
**Procedura**: `adauga_articol_factura` — spec `PF:537`, corp **`PF:4989`**,
|
||||
`END adauga_articol_factura;` **`PF:5284`**. **Deci DA, e `adauga_articol_factura`.**
|
||||
|
||||
**CORECTIE fata de raportul rundei 9**: procedura **nu scrie in `VANZARI_DETALII`**. Se termina cu un
|
||||
singur `INSERT`, `PF:5222`, in **`VANZARI_DETALII_TEMP`**:
|
||||
|
||||
```
|
||||
5222 INSERT INTO VANZARI_DETALII_TEMP (... PRET, PRET_CU_TVA, PROC_TVAV, ... ID_VALUTA, ... IN_STOC ...)
|
||||
5252 VALUES (... V_PRET, V_PRETURI_CU_TVA, V_PROC_TVAV, ... V_ID_VALUTA, ... V_IN_STOC ...)
|
||||
```
|
||||
|
||||
Re-derivarea se produce deci **pe drumul catre temp**, nu dupa el. Afirmatia „`scrie_in_vanzari`
|
||||
copiaza `PRET` neschimbat din temp" e adevarata (vezi 3) si totusi **irelevanta ca protectie**:
|
||||
valoarea din formular e inlocuita **inainte** sa intre in temp.
|
||||
|
||||
**Cine cheama procedura**: numai VFP-ul, din metoda de **salvare** a formularului:
|
||||
|
||||
- `COMUN\clase\ofacturare.vc2:14069` — `frm_facturare_articole.do_scrie_articole`
|
||||
- `COMUN\clase\ofacturare.vc2:18104` — `frm_facturare_articole2.do_scrie_articole`
|
||||
|
||||
(`:14054` si `:18089` sunt variante comentate, cu bind `?`, ale aceluiasi apel.)
|
||||
In pachet **nu exista apelant intern**: `grep 'adauga_articol_factura\b'` da doar `PF:537` (spec),
|
||||
`PF:4989` (corp), `PF:5284` (`END`) si doua comentarii de antet (`PF:22`, `PF:1497`).
|
||||
|
||||
**Traseul complet pe calea de scriere** (`frm_facturare_articole`):
|
||||
|
||||
1. `ofacturare.vc2:13981` — `pack_facturare.initializeaza_date_factura(...)`, care primeste
|
||||
`Alltrim(Str(poDate.Tip))` (`ofacturare.vc2:13994`) si il pune in variabila de pachet:
|
||||
`PF:1882 pack_facturare.ntip := V_TIP;` (corp `PF:1808`, `END` la `PF:1917`).
|
||||
**`ntip` = tipul documentului din formular** — acelasi care ajunge in `VANZARI.TIP` (`PF:13670`).
|
||||
Tot aici, `PF:1835 DELETE FROM VANZARI_DETALII_TEMP;` si `PF:1836 nid_act := 0;`.
|
||||
2. `ofacturare.vc2:14036-14115` — `SCAN` pe cursorul de grid `crsfactura`, cate un
|
||||
`pack_facturare.adauga_articol_factura(...)` per linie (`:14069`), executat prin
|
||||
`goExecutor.oExecute` (`:14105`).
|
||||
3. `frm_facturare_articole.do_scrie_factura` — `scrie_factura2` (`ofacturare.vc2:14345`, `:14373`),
|
||||
`scrie_factura_avize` (`:14318`) sau `scrie_factura_avize_retur` (`:14434`, `:14497`), care ajung
|
||||
la `pack_facturare.scrie_in_vanzari` (`PF:13488`).
|
||||
|
||||
**Raspuns Q1**: e `adauga_articol_factura`, si e pe calea de **scriere** a documentului (nu pe cea de
|
||||
creare/incarcare din sursa) — dar tinta insertului e `VANZARI_DETALII_TEMP`, iar re-derivarea se
|
||||
aplica **inainte** de insert, deci nimic de dupa temp nu o mai poate repara.
|
||||
|
||||
### Poarta care decide re-derivarea
|
||||
|
||||
`adauga_articol_factura` are un singur `CASE` (`PF:5052-5220`), pe **`pack_facturare.ntip`**:
|
||||
|
||||
| `WHEN` | linii | conditie |
|
||||
|---|---|---|
|
||||
| comenzi | `PF:5053-5078` | `ntip IN (3, 21, 28, 42, 47)` |
|
||||
| avize | `PF:5080-5103` | `ntip = 4` |
|
||||
| restaurant | `PF:5104-5145` | `ntip = 45` |
|
||||
| **contract** | `PF:5146-5185` | `V_OPT_FACTURARE = 3` |
|
||||
| `ELSE` | `PF:5187-5218` | restul |
|
||||
|
||||
`V_OPT_FACTURARE` se seteaza **numai** daca `ntip IN (2, 6, 26, 52)` (`PF:5039-5050`):
|
||||
`SELECT NVL(OPT_FACTURARE, 3) INTO V_OPT_FACTURARE FROM CONTRACTE WHERE ID_CTR = V_ID_CTR;`, cu
|
||||
`NO_DATA_FOUND -> V_OPT_FACTURARE := 4`. Pentru orice alt `ntip` ramane **NULL**, iar `WHEN NULL = 3`
|
||||
e NULL -> fals -> se merge pe `ELSE`. **Implicitul e ramura care re-deriva**: `NVL(OPT_FACTURARE, 3)`.
|
||||
|
||||
Tipurile (`COMUN\docs\tipuri_documente_facturare.md`): 2 = factura pe contract, 6 = contract in
|
||||
valuta, 26 = aviz pe contract, 52 = contract factura valuta; 4 = factura din avize;
|
||||
3/21/28/42/47 = din comenzi; 45 = restaurant.
|
||||
|
||||
## 2. Cele cinci valori, una cate una
|
||||
|
||||
Intai maparea parametrilor — ce **trimite** formularul (apel `ofacturare.vc2:14069-14091` confruntat
|
||||
cu semnatura `PF:4989-5015`; 27 de parametri, pozitional, potrivire completa):
|
||||
|
||||
| formular (`crsfactura` -> `poArt`) | parametru |
|
||||
|---|---|
|
||||
| `poArt.pretftva`/`pretctva`/`vpretftva`/`vpretctva` | `V_PRET_TEMP` |
|
||||
| `poArt.id_valuta` | `V_ID_VALUTA_TEMP` |
|
||||
| `poArt.cu_tva` | `V_PRETURI_CU_TVA_TEMP` |
|
||||
| `poArt.gestionabil` | `V_IN_STOC_TEMP` |
|
||||
| `poArt.id_jtva_coloana` | `V_ID_JTVA_COLOANA` |
|
||||
| `poArt.id_pol` | `V_ID_POL` |
|
||||
| `poArt.id_ctr` | `V_ID_CTR` |
|
||||
|
||||
**`PROC_TVAV` nu e parametru.** Nu exista `V_PROC_TVAV_TEMP` in semnatura — cota de TVA a liniei se
|
||||
calculeaza **intotdeauna** pe server, in **toate** ramurile. Formularul trimite `ID_JTVA_COLOANA`, din
|
||||
care ramura `ELSE` deriva `PROC_TVAV`. Pentru `PROC_TVAV` intrebarea „vine din temp?" **nu are raspuns
|
||||
„da" pe nicio ramura**; intrebarea reala e *din ce* se deriva.
|
||||
|
||||
### Ramura contract (`V_OPT_FACTURARE = 3`, `PF:5146-5185`)
|
||||
|
||||
- **`PRET`** — `DECODE(A.PRET_UNITAR, 0, V_PRET_TEMP, A.PRET_UNITAR)` (`PF:5149`).
|
||||
**Se re-deriva** din `CTR_ARTICOLE.PRET_UNITAR` ori de cate ori aceasta e `<> 0`, indiferent daca
|
||||
linia a fost atinsa pe ecran. Numai cand contractul are `PRET_UNITAR = 0` trece valoarea din
|
||||
formular. **Capcana NULL**: `CTR_ARTICOLE.PRET_UNITAR` e `nullable=Y` (verificat pe DB); un NULL
|
||||
**nu** potriveste literalul `0` in `DECODE`, deci rezultatul e **NULL**, nu `V_PRET_TEMP` — pretul
|
||||
din formular se pierde si linia intra in temp cu `PRET` NULL. *(dedus din semantica `DECODE`, nu
|
||||
rulat.)*
|
||||
- **`PROC_TVAV`** — `B.PROC_TVAV` din `CRM_POLITICI_PRET_ART` (`PF:5150`). **Se re-deriva** din
|
||||
politica de preturi, **nu** din `ID_JTVA_COLOANA` trimis de formular. Diferit de `ELSE`.
|
||||
- **`ID_VALUTA`** — `B.ID_VALUTA` din `CRM_POLITICI_PRET_ART` (`PF:5151`). **Se re-deriva**;
|
||||
`V_ID_VALUTA_TEMP` e ignorat.
|
||||
- **`PRET_CU_TVA`** — `A.PRET_CU_TVA` din `CTR_ARTICOLE` (`PF:5152`). **Se re-deriva**;
|
||||
`V_PRETURI_CU_TVA_TEMP` (`poArt.cu_tva`) e ignorat.
|
||||
- **`IN_STOC`** — `C.IN_STOC` din `NOM_ARTICOLE` (`PF:5153`). **Se re-deriva din nomenclatorul
|
||||
curent**; `V_IN_STOC_TEMP` (`poArt.gestionabil`) e ignorat.
|
||||
|
||||
Cele cinci **nu merg impreuna** nici macar aici: `PRET` are un `DECODE` care uneori lasa valoarea din
|
||||
formular sa treaca, celelalte patru sunt inlocuite **neconditionat**, din **trei tabele diferite**
|
||||
(`CTR_ARTICOLE`, `CRM_POLITICI_PRET_ART`, `NOM_ARTICOLE`).
|
||||
|
||||
**Iesirea de siguranta**: `EXCEPTION WHEN NO_DATA_FOUND` (`PF:5167-5185`) deriva `PROC_TVAV` din
|
||||
`JTVA_COLOANE` si pune **toate** celelalte patru pe valorile din formular (`PF:5181-5184`):
|
||||
`V_PRET := V_PRET_TEMP; V_ID_VALUTA := V_ID_VALUTA_TEMP; V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP;
|
||||
V_IN_STOC := V_IN_STOC_TEMP;`. Adica **daca linia nu mai are corespondent in contract, formularul
|
||||
castiga** — exact comportamentul cerut de decizia 54, existent deja in cod, dar numai pe calea de
|
||||
exceptie.
|
||||
|
||||
### Ramura `ELSE` (`PF:5187-5218`) — cazul „bun"
|
||||
|
||||
`PROC_TVAV` din `JTVA_COLOANE` pe `ID_JTVA_COLOANA` trimis de formular (`PF:5189-5192`), iar
|
||||
`PF:5200-5203` pune explicit `V_PRET := V_PRET_TEMP`, `V_ID_VALUTA := V_ID_VALUTA_TEMP`,
|
||||
`V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP`, `V_IN_STOC := V_IN_STOC_TEMP`.
|
||||
**Patru din cinci vin din formular; `PROC_TVAV` se deriva, dar din date trimise de formular.**
|
||||
|
||||
### Ramura comenzi (`ntip IN (3,21,28,42,47)`, `PF:5053-5078`)
|
||||
|
||||
```
|
||||
5057 SELECT A.PRET,
|
||||
5058 NVL2(A.PTVA, ROUND((A.PTVA + 100) / 100, 2), C.PROC_TVAV),
|
||||
5059 C.ID_VALUTA, B.PRETURI_CU_TVA, D.IN_STOC
|
||||
5075 WHERE A.ID_COMANDA = V_ID_COMANDA AND A.ID_ARTICOL = V_ID_ARTICOL
|
||||
5077 AND A.PRET = V_PRET_TEMP AND A.STERS = 0;
|
||||
```
|
||||
|
||||
- **`PRET`** — formal `A.PRET` din `COMENZI_ELEMENTE`, dar `WHERE ... A.PRET = V_PRET_TEMP`
|
||||
(`PF:5077`) **fixeaza** rezultatul pe pretul din formular. Practic **nu se schimba**.
|
||||
- **`PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`, `IN_STOC`** — **se re-deriva** din
|
||||
`COMENZI_ELEMENTE.PTVA` / `CRM_POLITICI_PRET_ART` / `CRM_POLITICI_PRETURI` / `NOM_ARTICOLE`.
|
||||
- **Ramura nu are bloc `EXCEPTION`.** Un `SELECT INTO` gol da `NO_DATA_FOUND` (ORA-01403)
|
||||
**netratat**, care urca prin `goExecutor` in VFP. Deci daca la reemitere pretul liniei a fost
|
||||
modificat pe ecran (sau linia nu mai e in comanda), scrierea **cade cu eroare** — nu se re-deriva
|
||||
tacit. Este o **a doua ramura de re-derivare**, pe care raportul rundei 9 nu o numara.
|
||||
|
||||
### Ramura restaurant (`ntip = 45`, `PF:5104-5145`)
|
||||
|
||||
`PF:5109-5112` pune explicit `V_PRET := V_PRET_TEMP`, `V_ID_VALUTA := V_ID_VALUTA_TEMP`,
|
||||
`V_PRETURI_CU_TVA := V_PRETURI_CU_TVA_TEMP`, `V_IN_STOC := V_IN_STOC_TEMP`; `SELECT`-ul de la
|
||||
`PF:5114-5139` deriva **doar** `V_PROC_TVAV` (din `JTVA_COLOANE`) si `V_PRET_ACHIZITIE`.
|
||||
Patru din cinci vin din formular.
|
||||
|
||||
### Ramura avize (`ntip = 4`) — la punctul 5
|
||||
|
||||
## 3. `UPDATE` / recalcul care suprascrie valorile DUPA insertul din temp
|
||||
|
||||
**Nu exista, pentru cele cinci valori.**
|
||||
|
||||
Scrierea temp -> definitiv, in `scrie_in_vanzari` (`PF:13488-13953`), e o copiere 1:1:
|
||||
|
||||
```
|
||||
13705 INSERT /*+ APPEND */
|
||||
13706 INTO VANZARI_DETALII (... PRET, PROC_TVAV, ... ID_VALUTA, ... PRET_CU_TVA, DIFERENTA, ...)
|
||||
13732 SELECT V_ID_VANZARE as ID_VANZARE, ... PRET, PROC_TVAV, ... ID_VALUTA, ... PRET_CU_TVA, DIFERENTA, ...
|
||||
13757 FROM VANZARI_DETALII_TEMP;
|
||||
```
|
||||
|
||||
> Capcana de cautare platita aici: `grep 'INSERT INTO VANZARI_DETALII'` da **zero** potriviri —
|
||||
> hint-ul `/*+ APPEND */` rupe cuvintele pe doua randuri. Cautarea corecta e `INTO VANZARI_DETALII`.
|
||||
|
||||
`IN_STOC` **nu apare** in lista de coloane (`PF:13707-13731`), si `VANZARI_DETALII` **nu are coloana
|
||||
`IN_STOC`** (verificat pe DB: `all_tab_columns` -> 0 randuri). Traieste doar in
|
||||
`VANZARI_DETALII_TEMP`, unde decide descarcarea de gestiune. Re-derivarea lui nu murdareste
|
||||
documentul salvat, dar **schimba comportamentul de stoc** al reemiterii.
|
||||
|
||||
Toate scrierile pe `VANZARI_DETALII` din pachet:
|
||||
|
||||
| linie | procedura | ce face |
|
||||
|---|---|---|
|
||||
| `PF:13705` | `scrie_in_vanzari` (`PF:13488`) | `INSERT` 1:1 din temp |
|
||||
| `PF:14974` | `finalizeaza_avize_lucrare` (`PF:14854`) | `INSERT` 1:1 din temp (cale avize de lucrare) |
|
||||
| `PF:5560` | `sterge_factura` (`PF:5432`) | `SET STERS, ID_UTILS, DATAORAS` |
|
||||
| `PF:5630` | `sterge_proforma` (`PF:5610`) | `SET STERS, ID_UTILS, DATAORAS` |
|
||||
| `PF:14516` | `modifica_explicatie_articol` (`PF:14511`) | `SET EXPLICATIE, TAXCODE` |
|
||||
| `PF:16017` | `actualizeaza_vanzari` (`PF:16012`) | `SET STERS = 0` |
|
||||
|
||||
**Niciun `UPDATE` nu atinge `PRET`, `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`.**
|
||||
Confirmat si pe DB: `all_source` are exact doua `INTO VANZARI_DETALII` (`LIVE:12466`, `LIVE:13735`).
|
||||
|
||||
Mutatiile pe `VANZARI_DETALII_TEMP` **intre** `adauga_articol_factura` si `scrie_in_vanzari`:
|
||||
|
||||
| linie | procedura | ce schimba |
|
||||
|---|---|---|
|
||||
| `PF:5297` | `adauga_diferente_pret` | numai `DIFERENTA` (`PF:5344`: `UPDATE SET DIFERENTA = B.DIFERENTA`) |
|
||||
| `PF:6860`, `PF:6864` | `scrie_factura_avize` | numai `CANTITATE` (split pe custodie); in `MERGE`, `PRET`, `PRET_CU_TVA`, `PROC_TVAV`, `ID_VALUTA`, `IN_STOC` sunt in clauza `ON`, nu in `UPDATE SET` |
|
||||
| `PF:14020` | `scrie_seturi` | numai `ID_VANZARE_SET` |
|
||||
| `PF:14057` | `scrie_seturi_proforma` | numai `ID_VANZARE_SET` (cale proforma) |
|
||||
| `PF:12266` | `transfera_articol` | cale de transfer, in afara scrierii de factura |
|
||||
|
||||
**`pack_auto.actualizeaza_deviz`** (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_31_02_AUTO_PACK_AUTO.sql:692-733`)
|
||||
atinge **`DEV_ORDL.PROC_TVAV`, `RUL.ID_FACT`, `NOM_LUCRARI.ID_FACT`** — **nu atinge `VANZARI_DETALII`**.
|
||||
|
||||
Recalculele de totaluri (`PF:13760+`, `recalculeaza_totaluri_vanzari` `PF:16021`) si rotunjirile
|
||||
lucreaza pe **`VANZARI`**, agregat — nu rescriu linia.
|
||||
|
||||
**Raspuns Q3**: nu. Dupa insertul in temp, cele cinci valori nu mai sunt suprascrise nicaieri.
|
||||
`DIFERENTA` si `CANTITATE` da, restul nu.
|
||||
|
||||
## 4. Verdict: ajung valorile din formular neatinse in `VANZARI_DETALII`?
|
||||
|
||||
**NU, pentru trei familii de tipuri. DA, pentru restul.**
|
||||
|
||||
Se pierd **intr-un singur loc**: `PACK_FACTURARE.adauga_articol_factura`, in `CASE`-ul de la
|
||||
**`PF:5052-5220`**, adica **inainte** de `INSERT INTO VANZARI_DETALII_TEMP` (`PF:5222`). Precis:
|
||||
|
||||
- **contract** (`ntip IN (2,6,26,52)` cu `CONTRACTE.OPT_FACTURARE` NULL sau 3) — `PF:5149-5153`:
|
||||
se pierd `PRET` (cand `CTR_ARTICOLE.PRET_UNITAR <> 0`), `PROC_TVAV`, `ID_VALUTA`, `PRET_CU_TVA`,
|
||||
`IN_STOC`;
|
||||
- **aviz** (`ntip = 4`) — `PF:5082-5086`: se pierd toate cinci, **inclusiv `PRET`, neconditionat**;
|
||||
- **comenzi** (`ntip IN (3,21,28,42,47)`) — `PF:5057-5061`: se pierd patru; `PRET` e fixat de
|
||||
`WHERE`, dar la nepotrivire scrierea **cade cu ORA-01403** in loc sa treaca.
|
||||
|
||||
Pentru toate celelalte tipuri (1, 5, 10, 22, 45, ...) valorile din formular **ajung neatinse**:
|
||||
ramurile `ELSE` (`PF:5200-5203`) si restaurant (`PF:5109-5112`) le copiaza explicit, iar traseul de
|
||||
dupa (`PF:13705-13757`) e copiere 1:1.
|
||||
|
||||
**Exceptie transversala, valabila pe TOATE tipurile**: `PROC_TVAV` **nu vine niciodata din formular** —
|
||||
nu e parametru al procedurii. Pe calea „buna" se deriva din `JTVA_COLOANE` pe `ID_JTVA_COLOANA`-ul
|
||||
trimis de formular, deci reproduce documentul **atat timp cat cota din `JTVA_COLOANE` nu s-a
|
||||
schimbat**. La o modificare legala de cota, reemiterea schimba TVA-ul chiar si pe ramura `ELSE`.
|
||||
|
||||
## 5. Sursa AVIZ (`ntip = 4`) — calea difera, si e mai grava
|
||||
|
||||
Verbatim, `PF:5080-5103` (confirmat identic pe DB, `LIVE:3840-3863`):
|
||||
|
||||
```
|
||||
5080 WHEN pack_facturare.ntip = 4 THEN
|
||||
5081 -- facturare din avize
|
||||
5082 SELECT DISTINCT A.PRET,
|
||||
5083 A.PROC_TVAV,
|
||||
5084 A.ID_VALUTA,
|
||||
5085 A.PRET_CU_TVA,
|
||||
5086 B.IN_STOC
|
||||
5087 INTO V_PRET, V_PROC_TVAV, V_ID_VALUTA, V_PRETURI_CU_TVA, V_IN_STOC
|
||||
5092 FROM VANZARI_DETALII A
|
||||
5093 LEFT JOIN NOM_ARTICOLE B ON A.ID_ARTICOL = B.ID_ARTICOL
|
||||
5095 WHERE A.ID_ARTICOL = V_ID_ARTICOL
|
||||
5096 AND A.ID_POL = V_ID_POL
|
||||
5097 AND NVL(A.ID_GESTIUNE, -1000) = V_ID_GESTIUNE
|
||||
5098 AND A.DISCOUNT_UNITAR = V_DISCOUNT_UNITAR
|
||||
5099 AND NVL(A.CONT, 'XXXX') = V_CONT
|
||||
5100 AND A.ID_VANZARE IN
|
||||
5101 (SELECT X AS ID_VANZARE
|
||||
5102 FROM table(cast(CHARN2COLLECTION(pack_facturare.clistaid, ',') AS num_tab)));
|
||||
```
|
||||
|
||||
Diferentele fata de contract, toate in defavoarea deciziei 54:
|
||||
|
||||
1. **`PRET` se re-deriva neconditionat**, direct din `VANZARI_DETALII` al **avizului sursa**. Nu exista
|
||||
`DECODE(..., 0, V_PRET_TEMP, ...)` si **nu exista `AND A.PRET = V_PRET_TEMP`** in `WHERE` (spre
|
||||
deosebire de ramura comenzi, `PF:5077`). Un pret modificat pe ecran la reemitere e **inlocuit
|
||||
tacit** cu pretul din aviz. **Aceasta e ramura cea mai agresiva din tot `CASE`-ul.**
|
||||
2. **Nu are bloc `EXCEPTION`.** Ramura contract are `NO_DATA_FOUND` care cade inapoi pe formular
|
||||
(`PF:5167-5185`); aici, orice modificare pe ecran a `DISCOUNT_UNITAR`, `CONT`, `ID_GESTIUNE` sau
|
||||
`ID_POL` scoate randul din `WHERE` -> **ORA-01403** urcata in VFP. `SELECT DISTINCT` peste mai
|
||||
multe avize cu preturi diferite pe acelasi articol poate da si **ORA-01422** (`TOO_MANY_ROWS`).
|
||||
3. **Nu filtreaza `A.STERS = 0`**, desi `VANZARI_DETALII` are coloana `STERS` (verificat pe DB) si
|
||||
`sterge_factura` o foloseste (`PF:5560`). Linii sterse ale avizului sursa pot fi citite.
|
||||
4. Depinde de `pack_facturare.clistaid` — lista de avize sursa, trimisa de VFP prin
|
||||
`initializeaza_date_factura` (`poDate.listaid`, `ofacturare.vc2:13992`). La reemitere, `clistaid`
|
||||
trebuie repopulata cu aceleasi avize, altfel `WHERE` nu potriveste nimic -> ORA-01403.
|
||||
|
||||
**Ce nu difera**: dupa `CASE` traseul e identic (`INSERT` in temp `PF:5222`, apoi copiere 1:1
|
||||
`PF:13705`), iar `scrie_factura_avize` (`PF:6692`) modifica in temp **numai `CANTITATE`**
|
||||
(`PF:6860`, `PF:6864`) — nu re-deriva nimic in plus.
|
||||
|
||||
**Raspuns Q5**: calea difera, si e **mai stricta**. Pe contract exista o portita (`PRET_UNITAR = 0`
|
||||
si `NO_DATA_FOUND`) prin care valorile din formular trec; pe aviz **nu exista niciuna** — ori se
|
||||
re-deriva tot, ori pica cu eroare.
|
||||
|
||||
## 6. Consecinta pentru decizia 54, la nivel de contract
|
||||
|
||||
### 6.1 Se poate curat VFP? **Nu.**
|
||||
|
||||
Confirm afirmatia rundei 9 — dar acum cu dovada, nu prin absenta:
|
||||
|
||||
- **Semnatura nu are flag.** `PF:4989-5015`: 27 de parametri, toti date de linie; ultimii doi
|
||||
(`V_TAXCODE`, `V_LOT`) sunt `DEFAULT NULL`, niciunul nu e comutator.
|
||||
- **Globalele pachetului nu au flag.** `PF:126-212` — stare de sesiune (`ntip`, `clistaid`,
|
||||
`nid_part`, ...), niciun comutator de comportament pe re-derivare.
|
||||
- **Poarta nu e influentabila legitim din VFP.** `ntip` ajunge in `VANZARI.TIP` (`PF:13670`) — a-l
|
||||
falsifica inseamna a schimba tipul documentului. `CONTRACTE.OPT_FACTURARE` e date de contract, nu
|
||||
parametru de apel.
|
||||
- **Singurele parghii VFP care ar devia pe calea `NO_DATA_FOUND` sunt, ambele, de aceeasi natura ca
|
||||
varianta (d) deja respinsa:**
|
||||
- `V_ID_CTR = NULL` — respinsa; `PF:5280` duce `V_ID_CTR` in `temp.ID_CTR`, iar `PF:13755` in
|
||||
`VANZARI_DETALII.ID_CTR`, deci legatura se pierde permanent;
|
||||
- `V_ID_POL = NULL` — **acelasi defect, alta coloana**: `PF:5258` duce `V_ID_POL` in `temp.ID_POL`,
|
||||
`PF:13737` in `VANZARI_DETALII.ID_POL`; in plus ar rupe ramura aviz, care potriveste pe
|
||||
`A.ID_POL = V_ID_POL` (`PF:5096`). **Nu o propun** — o mentionez ca sa nu fie redescoperita ca
|
||||
„solutie" intr-o runda urmatoare.
|
||||
|
||||
**Concluzie: decizia 54 cere obligatoriu modificare in `pack_facturare`.**
|
||||
|
||||
### 6.2 Ce forma trebuie sa aiba semnalul
|
||||
|
||||
Doua forme sunt inerte pentru apelantii de azi:
|
||||
|
||||
- **(i) variabila noua de pachet** („regenerare in curs"), implicit `0` = comportamentul actual, scrisa
|
||||
de VFP **dupa** `initializeaza_date_factura` si inainte de bucla de `adauga_articol_factura`;
|
||||
- **(ii) parametru `DEFAULT 0`** adaugat **dupa** `V_LOT` in semnatura — apelantii de azi trimit 27 de
|
||||
argumente pozitional si raman valizi.
|
||||
|
||||
**Recomand (i)**, din trei motive concrete: se potriveste cu stilul existent al pachetului (care e deja
|
||||
o punga de stare de sesiune); se scrie o data pe document, nu o data pe linie, in toate produsele; si
|
||||
**punctul de resetare exista deja** — `initializeaza_date_factura` (`PF:1808-1917`) reinitializeaza
|
||||
~40 de globale si face `DELETE FROM VANZARI_DETALII_TEMP` (`PF:1835`), deci flag-ul nu poate scapa in
|
||||
documentul urmator din aceeasi sesiune. Atentie la ordine: VFP il seteaza **dupa** apelul de la
|
||||
`ofacturare.vc2:13981`, nu inainte. Ambele variante modifica spec-ul, deci ambele forteaza
|
||||
recompilarea dependentilor — nu e un criteriu de departajare.
|
||||
|
||||
### 6.3 Ce face semnalul, cand e pornit
|
||||
|
||||
**Nimic nou** — forteaza ramura care exista deja. Cea mai mica forma: un `WHEN` nou, **primul** in
|
||||
`CASE`-ul de la `PF:5052`, cu **acelasi corp ca `ELSE`-ul de azi** (`PF:5189-5203`): `PROC_TVAV` din
|
||||
`JTVA_COLOANE` pe `V_ID_JTVA_COLOANA`, si `V_PRET`/`V_ID_VALUTA`/`V_PRETURI_CU_TVA`/`V_IN_STOC` din
|
||||
parametrii `*_TEMP`. `INSERT`-ul de la `PF:5222` ramane neatins. Acopera dintr-o data **toate trei**
|
||||
ramurile problematice (contract, aviz, comenzi), pentru ca toate sunt in acelasi `CASE`.
|
||||
|
||||
### 6.4 Trei consecinte de acceptat explicit, nu ocolite
|
||||
|
||||
1. **`IN_STOC` ar veni din formular** (`poArt.gestionabil`) in loc de `NOM_ARTICOLE`. Nu e o problema
|
||||
de fidelitate a documentului — `VANZARI_DETALII` **nu are coloana `IN_STOC`** (verificat pe DB) —
|
||||
ci de **comportament de stoc** la reemitere. Ca reemiterea sa fie identica, incarcarea (S8) trebuie
|
||||
sa dea valoarea cu care s-a scris documentul initial. **Azi nu o da**: loader-ul lui #6 o citeste
|
||||
din nomenclatorul curent — `ofacturare_editare.prg:302-303`,
|
||||
`left join nom_articole na on na.id_articol = v.id_articol`, cu comentariul care spune ca view-ul
|
||||
n-o expune. **Flag-ul singur nu rezolva asta.**
|
||||
2. **Se pierde o validare pe ramura comenzi.** `A.PRET = V_PRET_TEMP` (`PF:5077`) plus lipsa lui
|
||||
`EXCEPTION` fac azi ca o linie care nu se mai potriveste cu comanda sa **opreasca** scrierea.
|
||||
Cu flag-ul pornit, reemiterea unei „facturi din comanda" nu mai verifica linia fata de comanda.
|
||||
Decizia 54 pare sa vrea exact asta, dar e o schimbare de comportament care trebuie spusa, nu
|
||||
descoperita la S12.
|
||||
3. **`PROC_TVAV` ramane derivat**, nu preluat. Reproducerea exacta a documentului cere ca S8 sa
|
||||
pastreze `ID_JTVA_COLOANA` **si** ca `JTVA_COLOANE.COTA_TVA` sa nu se fi schimbat intre timp. Daca
|
||||
se cere reproducere exacta si peste o modificare de cota, `PROC_TVAV` trebuie sa devina
|
||||
**parametru** — schimbare mai mare decat flag-ul, de decis separat.
|
||||
|
||||
### 6.5 Un obstacol de incarcare, gasit in treacat (pentru S8, nu pentru S10)
|
||||
|
||||
View-ul pe care se sprijina incarcarea de azi, **`VVANZARI_ARTICOLE`**, expune (interogat pe DB):
|
||||
`ID_VANZARE, 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,
|
||||
ID_VANZARE_SET, PRET_ACHIZITIE, DENUMIRE, CODMAT, NUME_GESTIUNE, NUME_VAL`.
|
||||
|
||||
**Nu expune `ID_POL` si nu expune `ID_CTR`** — exact cei doi pe care `adauga_articol_factura` ii cere
|
||||
(`V_ID_POL`, `V_ID_CTR`) si pe care `VANZARI_DETALII` ii pastreaza. Reemiterea S9 **nu poate folosi
|
||||
acest view ca atare**; ori se completeaza view-ul, ori loader-ul citeste direct din
|
||||
`VANZARI_DETALII`. Nu e in perimetrul S10, dar blocheaza S9 daca nu e prins din timp.
|
||||
|
||||
---
|
||||
|
||||
## Tabel sintetic — cele cinci valori pe ramura
|
||||
|
||||
„temp" = valoarea trimisa de formular; „re-derivat" = citita din alta sursa si suprascrisa.
|
||||
|
||||
| valoare | **contract** (2/6/26/52, `OPT_FACTURARE` NULL sau 3) | **aviz** (`ntip = 4`) | comenzi (3/21/28/42/47) | restaurant (45) | `ELSE` |
|
||||
|---|---|---|---|---|---|
|
||||
| `PRET_UNITAR` (`PRET`) | **re-derivat** din `CTR_ARTICOLE.PRET_UNITAR` daca `<> 0`; **temp** daca `= 0`; **NULL** daca e NULL (`PF:5149`) | **re-derivat** din `VANZARI_DETALII` al avizului, **neconditionat** (`PF:5082`) | **temp** de facto — `WHERE A.PRET = V_PRET_TEMP` (`PF:5077`); nepotrivire = ORA-01403 | **temp** (`PF:5109`) | **temp** (`PF:5200`) |
|
||||
| `PROC_TVAV` | **re-derivat** din `CRM_POLITICI_PRET_ART` (`PF:5150`) | **re-derivat** din avizul sursa (`PF:5083`) | **re-derivat** din `COMENZI_ELEMENTE.PTVA` / politica (`PF:5058`) | **re-derivat** din `JTVA_COLOANE` (`PF:5114`) | **derivat** din `JTVA_COLOANE` pe `ID_JTVA_COLOANA` din formular (`PF:5189`) |
|
||||
| `ID_VALUTA` | **re-derivat** din `CRM_POLITICI_PRET_ART` (`PF:5151`) | **re-derivat** din avizul sursa (`PF:5084`) | **re-derivat** din politica (`PF:5059`) | **temp** (`PF:5110`) | **temp** (`PF:5201`) |
|
||||
| `PRET_CU_TVA` | **re-derivat** din `CTR_ARTICOLE` (`PF:5152`) | **re-derivat** din avizul sursa (`PF:5085`) | **re-derivat** din `CRM_POLITICI_PRETURI` (`PF:5060`) | **temp** (`PF:5111`) | **temp** (`PF:5202`) |
|
||||
| `IN_STOC` | **re-derivat** din `NOM_ARTICOLE` (`PF:5153`) | **re-derivat** din `NOM_ARTICOLE` (`PF:5086`) | **re-derivat** din `NOM_ARTICOLE` (`PF:5061`) | **temp** (`PF:5112`) | **temp** (`PF:5203`) |
|
||||
|
||||
`PROC_TVAV` **nu vine din temp pe nicio ramura** — nu e parametru al procedurii.
|
||||
`IN_STOC` **nu ajunge in `VANZARI_DETALII`** (coloana nu exista) — traieste doar in temp, unde decide
|
||||
descarcarea de gestiune.
|
||||
Pe ramura contract, `EXCEPTION WHEN NO_DATA_FOUND` (`PF:5167-5185`) muta **toate cele cinci** pe
|
||||
coloana „temp"; ramurile aviz si comenzi **nu au** un asemenea bloc.
|
||||
|
||||
## Verificat direct vs. dedus
|
||||
|
||||
**Verificat direct pe fisier SI confirmat pe DB** (`all_source`, `MARIUSM_AUTO.PACK_FACTURARE`,
|
||||
`PACKAGE BODY`, VALID, `last_ddl_time = 2026-08-09 20:03:50`):
|
||||
granitele lui `adauga_articol_factura`; toate cele cinci ramuri ale `CASE`-ului, ramura cu ramura;
|
||||
textul integral al ramurii aviz; faptul ca insertul procedurii merge in `VANZARI_DETALII_TEMP`; ca
|
||||
exista exact doua `INTO VANZARI_DETALII` in tot pachetul.
|
||||
|
||||
**Verificat direct pe fisier** (nereconfirmat separat pe DB, dar in aceeasi zona cu offset masurat):
|
||||
copierea 1:1 temp -> `VANZARI_DETALII` (`PF:13705-13757`); cele patru `UPDATE VANZARI_DETALII` si ce
|
||||
coloane ating; mutatiile pe temp intre `adauga_articol_factura` si `scrie_in_vanzari`; corpul lui
|
||||
`pack_auto.actualizeaza_deviz`.
|
||||
|
||||
**Verificat pe metadatele DB**: `VANZARI_DETALII` nu are `IN_STOC`, are `STERS`;
|
||||
`CTR_ARTICOLE.PRET_UNITAR` e `nullable=Y`; lista de coloane a lui `VVANZARI_ARTICOLE`.
|
||||
|
||||
**Verificat direct in VFP**: apelantii lui `adauga_articol_factura`; maparea celor 27 de parametri;
|
||||
`ntip := poDate.Tip`; loader-ul `IncarcaArticoleFactura` (`ofacturare_editare.prg:292-327`).
|
||||
|
||||
**Dedus, nerulat**: comportamentul `DECODE` cu `PRET_UNITAR` NULL (semantica Oracle, nu test);
|
||||
ORA-01403 / ORA-01422 pe ramurile fara `EXCEPTION` (din absenta blocului, nu din reproducere).
|
||||
|
||||
**Din date de dev — NU e dovada, nu extrapolati**: `CTR_ARTICOLE` are **27** de randuri
|
||||
(0 NULL, 10 cu `PRET_UNITAR = 0`, 17 cu `<> 0`) — deci **cazul NULL nu apare in dev**, ceea ce nu
|
||||
spune nimic despre productie. `CONTRACTE.OPT_FACTURARE`: 176 NULL, 53 = 0, 9 = 1, 3 = 3, 1 = 4 —
|
||||
adica pentru **176 + 3 din 242** de contracte `NVL(OPT_FACTURARE, 3)` da 3 si ramura re-deriva.
|
||||
|
||||
**Neacoperit**: n-am rulat niciun flux real (nici VFP, nici o reemitere efectiva) — totul e analiza
|
||||
statica plus interogari `SELECT` pe metadate. N-am citit corpurile `scrie_factura2` si
|
||||
`finalizeaza_factura` integral, doar punctele in care ating `VANZARI_DETALII` / temp.
|
||||
|
||||
## Corectii la rapoartele anterioare
|
||||
|
||||
### `docs\cercetare\s10_pret_rederivat.md` (runda 9)
|
||||
|
||||
1. **„exista exact o ramura care suprascrie pretul — contractul" — FALS.** Ramura **aviz**
|
||||
(`ntip = 4`, `PF:5080-5103`) suprascrie `PRET` **neconditionat**, fara `DECODE` si fara filtru pe
|
||||
pretul din formular — **mai agresiv decat contractul**. Ramura **comenzi** (`PF:5053-5078`) nu
|
||||
suprascrie `PRET`, dar suprascrie celelalte patru si **arunca ORA-01403** la nepotrivire.
|
||||
Ramurile care re-deriva sunt **trei**, nu una.
|
||||
2. **„pe calea de scriere" — corect ca moment, gresit ca destinatie.** Se intampla la salvare, dar
|
||||
insertul e in **`VANZARI_DETALII_TEMP`** (`PF:5222`), nu in `VANZARI_DETALII`. Diferenta conteaza:
|
||||
explica de ce copierea „neschimbata" de mai tarziu nu protejeaza nimic.
|
||||
3. **„nu exista niciun parametru sau flag care sa opreasca re-derivarea" — CONFIRMAT**, acum cu dovada
|
||||
pozitiva (semnatura `PF:4989-5015`, globalele `PF:126-212`), nu prin absenta.
|
||||
|
||||
### `docs\cercetare\s10_pret_contract_reemitere.md` (runda 13)
|
||||
|
||||
1. **„acelasi `SELECT` alimenteaza cinci valori" — corect** (`PF:5149-5166`), dar de completat:
|
||||
**nu merg impreuna** — `PRET` are `DECODE`, celelalte patru sunt neconditionate, si vin din trei
|
||||
tabele diferite.
|
||||
2. **„`IN_STOC` din nomenclatorul curent" — corect**, de completat cu faptul decisiv:
|
||||
**`VANZARI_DETALII` nu are coloana `IN_STOC`** (verificat pe DB). Nu ajunge in document; efectul e
|
||||
pe descarcarea de gestiune.
|
||||
3. **„`scrie_in_vanzari` copiaza `PRET` neschimbat din temp" — corect** (`PF:13705-13757`), de marcat
|
||||
insa ca **nu e o protectie**: dauna e amonte de temp.
|
||||
4. Varianta **„avertizare + confirmare" ramane respinsa** (decizia 54) — nu am reargumentat-o.
|
||||
|
||||
### `docs\plan_13_unificare_formular_facturare.md`
|
||||
|
||||
- Trimiterea „`initializeaza_date_factura` (`PACK_FACTURARE:1808-1846`)" — corpul incepe la `PF:1808`
|
||||
dar se termina la **`PF:1917`**. Citarile `:1835` / `:1836` din plan sunt corecte.
|
||||
379
docs/cercetare/s11_legaturi_id_vanzare.md
Normal file
379
docs/cercetare/s11_legaturi_id_vanzare.md
Normal file
@@ -0,0 +1,379 @@
|
||||
# S11 — Legaturile care raman pe `ID_VANZARE` la editarea prin regenerare
|
||||
|
||||
Stare: **in lucru**. Vezi sectiunea STARE / CE RAMANE la final pentru progres curent.
|
||||
|
||||
## Context (dat, nu se rediscuta)
|
||||
|
||||
#13 etapa II: editarea unei facturi = regenerare. Documentul vechi e soft-sters (`STERS=1`) si
|
||||
reemis in aceeasi tranzactie. `ID_FACT`, seria, numarul si data se pastreaza. **`ID_VANZARE` se
|
||||
schimba** — documentul reemis primeste un `id_vanzare` nou din secventa.
|
||||
|
||||
Sarcina: inventarul complet al legaturilor pe `VANZARI.ID_VANZARE` care se rup la aceasta
|
||||
schimbare, clasificate (A) trebuie remigrat / (B) trebuie sters-refacut / (C) nu conteaza.
|
||||
|
||||
## 0. Puncte de plecare (de verificat, nu de preluat pe incredere)
|
||||
|
||||
- `docs\plan_13_unificare_formular_facturare.md` — sectiunea `#### S11` (linia ~3024)
|
||||
- `docs\cercetare\idfact_refolosire_si_documente.md`
|
||||
- `docs\cercetare\rec_cale_vanzari_detalii.md`
|
||||
- `COMUN\docs\cercetare\rec_consumatori_vanzari.md`
|
||||
- `docs\cercetare\cont_venit_corespondente.md`
|
||||
- `docs\cercetare\legatura_linie_retur.md`
|
||||
- `docs\cercetare\rec_d42_efactura.md`
|
||||
- `docs\cercetare\s5c_factura_din_proforma.md` (sectiunea `VANZARI_CORESP`)
|
||||
- PL/SQL: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
|
||||
## 0-bis. Constatare centrala care schimba premisa din plan
|
||||
|
||||
**Mecanismul S9 (planificat) NU e acelasi cu mecanismul deja existent de editare (#6).** Astea sunt
|
||||
doua cai distincte, si asta conteaza pentru fiecare consumator de mai jos:
|
||||
|
||||
- **#6, "editare directa"** (`frm_modific2024`/`omodificari.vc2`, `afisjurcom.do_modifica`,
|
||||
`pack_contafin.finalizeaza_modificare_nota`): documentul vechi (identificat prin `cod`) primeste
|
||||
`STERS=1` in `ACT`/`RUL`, se scrie un document nou cu `cod` nou, apoi
|
||||
**`pack_facturare.actualizeaza_vanzari(V_COD_VECHI, V_COD_NOU)`**
|
||||
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16012-16022`) face
|
||||
`UPDATE VANZARI SET COD = V_COD_NOU, STERS = 0 WHERE COD = V_COD_VECHI` —
|
||||
**randul `VANZARI` e REFOLOSIT, `ID_VANZARE` NU SE SCHIMBA**, doar `COD`. In continuare,
|
||||
`finalizeaza_modificare_nota` face si `UPDATE atasamente_vanzari SET cod = lnCodNou WHERE cod =
|
||||
tnCod` (`rec_modific2024.md:126`, confirmat `fisier:linie`). Acesta e mecanismul deja **livrat in
|
||||
productie** (git log: `#6 editare factura emisa`, changelog 2.11.15/2.11.16).
|
||||
- **#13 S9 (planificat, subiectul acestei cercetari)**: reemiterea merge **pe drumul normal de
|
||||
emitere**, `scrie_factura2` -> `finalizeaza_factura` -> `scrie_in_vanzari`, care face
|
||||
`INSERT INTO VANZARI (...) VALUES (...) RETURNING ID_VANZARE INTO pack_facturare.nid_vanzare`
|
||||
(`rec_cale_vanzari_detalii.md:99`, confirmat separat in planul S9: „reemiterea scrie prin
|
||||
`pack_facturare`, pe acelasi drum ca emiterea", `plan_13...md:2936-2937`). Acesta e un rand **nou**,
|
||||
cu **`ID_VANZARE` nou din secventa** — exact premisa data de team-lead. `oscrie_in_fisiere`
|
||||
intervine in S9 **doar la stergerea** documentului vechi (`plan_13...md:2938`), nu si la scriere,
|
||||
deci **`actualizeaza_vanzari`/sincronizarea prin `finalizeaza_modificare_nota` NU se declanseaza
|
||||
pentru S9** — acel mecanism ramane specific caii #6, nu se mosteneste automat de S9.
|
||||
|
||||
**Consecinta directa**: orice legatura tinuta azi prin `cod` (realiniata gratis de mecanismul #6) e
|
||||
**expusa** la regenerarea din #13, pentru ca S9 nu trece prin `actualizeaza_vanzari`. Fiecare sectiune
|
||||
de mai jos noteaza explicit daca protectia #6 s-ar fi aplicat sau nu.
|
||||
|
||||
## 1. Inventarul exhaustiv al consumatorilor de ID_VANZARE
|
||||
|
||||
| # | Tabela/mecanism | Cheie folosita | Scriitor(i) | Cititor(i) | Clasificare |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `ATASAMENTE_VANZARI` | `ID_VANZARE` (FK real `FK_AT_VANZ001` -> `VANZARI.ID_VANZARE`) + coloana `COD` (legacy, vezi §2) | `ofacturare_comun.prg:466` (VFP INSERT direct), `pack_facturare.scrie_atasamente_factura` (PL/SQL, `ff_...:13955-13988`) | `VATASAMENTE_VANZARI` (view, JOIN pe `id_vanzare`), `ROAGEST`/`ROAIMOB` `oproceduri_atasamente.prg` (`citeste_atasament_vanzari`, `arata_meniu_at_vanz` — cauta pe `cod` via view) | **(A) trebuie remigrat** — vezi §2 |
|
||||
| 2 | `marcheaza_facturat` -> `VANZARI.FACTURAT`/`ID_UTILFACT` | `VANZARI_CORESP.ID_VANZARE_FACT` (documentul nou) leaga la sursa | `pack_facturare.marcheaza_facturat`, apelata din `finalizeaza_factura` (`ff_...:14827,14831`) | citit la filtrarea avizelor/comenzilor facturabile | **(C) nu conteaza pentru discontinuitatea ID_VANZARE-ului documentului editat** — vezi §3 (releaga sursa, nu documentul editat insusi) |
|
||||
| 3 | `VANZARI_CORESP` | `ID_VANZARE_FACT`/`ID_VANZARE_AVIZ`, ambele pe `VANZARI.ID_VANZARE` | `pack_facturare.scrie_corespondente_vanzari`, singurul writer (`s5c_factura_din_proforma.md:92-96`) | `sterge_factura` (garda la stergere), afisare "provine din X" | **(A) trebuie remigrat daca documentul editat e parte a unui lant retur/aviz->factura** — vezi §3 |
|
||||
| 4 | `VANZARI_CANTITATI` | `ID_VANZARE` (a avizului sursa, nu a facturii editate) | `scrie_cantitati_vanzari_avize`, apelata doar `ntip=4` | `marcheaza_facturat(V_VERIFICARE=1)` | **(C) nu conteaza** — vezi §3, tine de sursa, neatinsa de editarea facturii |
|
||||
| 5 | `DOCUMENTE` | `ID_DOC` = `ID_FACT` (nu `ID_VANZARE`) | `SET_IDFACT`/INSERT in `SCRIE_IN_ACT` | `SET_IDFACT` cautare veche (inactiva) | **(C) confirmat fara legatura cu ID_VANZARE** — vezi §4, acoperit deja de `idfact_refolosire_si_documente.md`, nu se reface |
|
||||
| 6 | eFactura (`ANAF_EFACTURA`, `EsteInEFactura`) | `VANZARI.ID_FACT` (nu `ID_VANZARE`) | — | `ofacturare_editare.prg`/`omodificari.vc2` | **(C) nu conteaza** — cheia e `ID_FACT`, care se pastreaza — vezi §5 |
|
||||
| 7 | Listari/rapoarte facturi (`FACT_VFACTURI*`, grid cautare) | `ID_VANZARE` + `ID_FACT`/serie+numar, cautari mixte | — | multiple | **de verificat, vezi §5** — cele care cauta dupa serie+numar/ID_FACT raman corecte; cele care tin un `ID_VANZARE` stocat undeva (ex. favorite, ultima factura deschisa) s-ar rupe |
|
||||
| 8 | Incasari/plati (`INCASARI`, `PLATI`, `IREG_PARTENERI`) | **de verificat** | — | — | **in lucru, vezi §6** |
|
||||
|
||||
## 2. ATASAMENTE_VANZARI
|
||||
|
||||
**Nu sunt documente incarcate de utilizator — sunt exporturi PDF AUTO-GENERATE ale documentului
|
||||
tiparit** (factura/aviz/recapitulatie/invoice), salvate automat dupa listare. Dovada, pas cu pas:
|
||||
|
||||
- Scrierea porneste din `frm_facturi.do_listeaza_formular`/fluxul de listare, la
|
||||
`COMUN\programe\ofacturare.prg:2102-2105`:
|
||||
```
|
||||
If poDate.nRelistare = 0 And poDate.eProforma = 0 AND poDate.nEFactura = 0 And poDate.nSalveazaAtasamente = 1
|
||||
poDate.scrieAtasamente()
|
||||
Endif
|
||||
```
|
||||
imediat dupa exportul PDF al recapitulatiei (`:2068`, `goExport.export2pdf('crsrecapitulatie',
|
||||
'recapitulatie', .F., poDate.cDocAtasate)`) — `poDate.cDocAtasate` **e** numele cursorului
|
||||
`crsoDateDocAtasate`, populat de motorul de export PDF, nu de un dialog de upload.
|
||||
- `crsoDateDocAtasate` se creeaza gol la `ofacturare.prg:233`: `Create Cursor crsoDateDocAtasate
|
||||
(nume_frx c(50), fisier w)` — nicio referinta la `GETFILE()`/`GETPICT()`/dialog de fisier in tot
|
||||
`ROAFACTURARE`/`COMUN` legata de acest cursor (cautat explicit, zero potriviri).
|
||||
- `scrieAtasamente` (`ofacturare_comun.prg:429-475`) clasifica `TIP` dupa numele raportului
|
||||
(`FACTURA_VAL*`->2, `FACTURA*`->1, `INVOICE*`->3, `RECAPITULATIE`->4, `AVIZ*`->5) si scrie:
|
||||
`INSERT INTO ATASAMENTE_VANZARI(ID_VANZARE, TIP, FORMAT, DOCUMENT, ID_UTIL) VALUES (?pnId, ...)`
|
||||
(`:466`) — `pnId = poDate.nid_vanzare` (sau `nid_vanzare_retur` pentru cazul aviz->factura). Deci
|
||||
scriitorul **foloseste exclusiv `ID_VANZARE`**, niciodata `COD`.
|
||||
- Cititorii confirmati: `ROAGEST\COMUN\programe\oproceduri_atasamente.prg` (identic in `ROAIMOB`) —
|
||||
`citeste_atasament_vanzari` (`SELECT document FROM atasamente_vanzari WHERE id_at_vanz=...`) si
|
||||
`arata_meniu_at_vanz(tnCod,...)` (`SELECT ... FROM vatasamente_vanzari a WHERE a.cod = ...`,
|
||||
`:56`) — ambele read-only, mecanism de "vezi documentele salvate pentru aceasta factura",
|
||||
disponibil din ROAGEST/ROAIMOB (alte produse ale suitei, confirmand ca S11 trebuia sa caute in
|
||||
toata suita, nu doar ROAFACTURARE).
|
||||
|
||||
**Structura reala (interogata pe schema vie `MARIUSM_AUTO`)**: `ATASAMENTE_VANZARI(ID_AT_VANZ PK
|
||||
NOT NULL din secventa, ID_VANZARE NUMBER NULL, DOCUMENT BLOB, TIP, FORMAT, STERS NOT NULL, ID_UTIL,
|
||||
DATAORA NOT NULL, ID_UTILS, DATAORAS, COD NUMBER NULL)`. FK real: `FK_AT_VANZ001` pe `ID_VANZARE ->
|
||||
VANZARI.ID_VANZARE` (`PK_VANZARI`). Coloana `COD` **nu e scrisa de niciun cod curent** (scriitorii
|
||||
gasiti folosesc doar `ID_VANZARE`) — e populata azi doar de mecanismul legacy #6
|
||||
(`finalizeaza_modificare_nota`'s `UPDATE atasamente_vanzari SET cod = lnCodNou WHERE cod = tnCod`,
|
||||
care **nu poate seta `cod` pe un rand care nu-l are deja** — e un realiniere, nu o initializare).
|
||||
Pe schema de test `MARIUSM_AUTO`, toate cele 20 de randuri existente au `COD` populat si
|
||||
`ID_VANZARE` NULL (probabil date vechi, dinainte ca `ID_VANZARE` sa fie coloana activa) — cf.
|
||||
memoriei de proiect, **zero cazuri in date nu e dovada**, dar dovada de cod (scriitorii folosesc
|
||||
doar `ID_VANZARE`) e independenta de date si sta singura.
|
||||
|
||||
**Cheia reala de legatura azi e `ID_VANZARE`, prin view**: `VATASAMENTE_VANZARI` (definitie
|
||||
interogata direct din `ALL_VIEWS`):
|
||||
```sql
|
||||
select a.id_at_vanz, a.tip, decode(a.tip,1,'Factura',2,'Factura in valuta',3,'Invoice',
|
||||
4,'Recapitulatie',5,'Aviz','Alt document') as tip_doc, b.cod
|
||||
from atasamente_vanzari a
|
||||
left join vanzari b on a.id_vanzare = b.id_vanzare
|
||||
where a.sters = 0 and b.sters = 0
|
||||
```
|
||||
`cod` in view **nu e coloana stocata pe `atasamente_vanzari`** — e `VANZARI.COD` curent, calculat
|
||||
live prin JOIN pe `ID_VANZARE`. Consumatorii din alta suita (`ROACONT\Programe\orap_terti.prg:1854`,
|
||||
`ROACONTRACTE\Programe\oparteneri_contracte.prg:190,229` — `LEFT JOIN vatasamente_vanzari ... ON
|
||||
a.cod = b.cod`, pentru afisarea unui numar de atasamente pe rand de factura) folosesc de fapt tot
|
||||
`ID_VANZARE`, indirect prin acest JOIN.
|
||||
|
||||
**Ce se rupe la regenerare (S9, calea B din §0-bis)**: `atasamente_vanzari.id_vanzare` al randurilor
|
||||
vechi ramane neschimbat, aratand spre `VANZARI` cu `STERS=1` dupa stergerea documentului vechi.
|
||||
`WHERE b.sters = 0` din view **filtreaza acele randuri afara** — atasamentele **dispar tacit** din
|
||||
orice interogare prin `VATASAMENTE_VANZARI` (inclusiv `arata_meniu_at_vanz` din ROAGEST/ROAIMOB),
|
||||
desi BLOB-ul ramane fizic in tabel. Documentul nou (`ID_VANZARE` nou) nu are niciun atasament legat.
|
||||
**Clasificare: (A) trebuie remigrat** — `UPDATE atasamente_vanzari SET id_vanzare = :id_nou WHERE
|
||||
id_vanzare = :id_vechi AND sters = 0`, in aceeasi tranzactie ca restul regenerarii (acelasi tipar ca
|
||||
`actualizeaza_vanzari` de la #6, dar pe `id_vanzare` in loc de `cod`, si trebuie scris nou — #6 nu
|
||||
acopera acest caz, cf. §0-bis).
|
||||
|
||||
**Corectie fata de premisa din briefing**: nu e "pierdere de date reala" in sensul de date
|
||||
introduse de utilizator si irecuperabile — snapshot-ul se poate regenera prin relistare (acelasi
|
||||
mecanism care l-a creat prima data). Ce s-ar pierde real e **istoricul exact al PDF-ului trimis
|
||||
clientului la momentul emiterii initiale** (relevant daca factura a fost deja trimisa/tiparita
|
||||
inainte de editare) — merita remigrare oricum, ca sa nu se piarda urma, dar motivatia corecta e
|
||||
"pastrarea unui audit trail", nu "date introduse de utilizator".
|
||||
|
||||
## 3. marcheaza_facturat / VANZARI.FACTURAT / VANZARI_CANTITATI / VANZARI_CORESP
|
||||
|
||||
**Inventarul FK declarat pe `VANZARI.ID_VANZARE`** (interogare directa pe schema `ACN`, cea reala —
|
||||
`ALL_CONS_COLUMNS`/`ALL_CONSTRAINTS`, `r_constraint_name = PK_VANZARI`), confirma si extinde lista
|
||||
din briefing:
|
||||
|
||||
| Tabela | Coloana | Constraint |
|
||||
|---|---|---|
|
||||
| `ATASAMENTE_VANZARI` | `ID_VANZARE` | `FK_AT_VANZ001` |
|
||||
| `VANZARI_CORESP` | `ID_VANZARE_FACT` | `FK_VANZARI_CORESP_001` |
|
||||
| `VANZARI_CORESP` | `ID_VANZARE_AVIZ` | `FK_VANZARI_CORESP_002` |
|
||||
| `VANZARI_CURSURI` | `ID_VANZARE` | `FK_VANZARE_CURS_002` |
|
||||
| `VANZARI_DETALII` | `ID_VANZARE` | `FK_VANZARE_DET001` |
|
||||
| `REST_NOTE_PLATA` | `ID_VANZARE` | `FK_REST_NOTE_PLATA_004` |
|
||||
| `IPS_VOYAGES_VANZARI` | `VZ_ID` | `FK_VOYAGES_VANZARI_2` |
|
||||
|
||||
Primele cinci erau asteptate (vezi §1-2 si mai jos). **Ultimele doua nu erau in lista din plan** —
|
||||
descoperite prin FK, nu prin cautare de text, exact motivul pentru care inventarul trebuia facut pe
|
||||
schema, nu doar pe cod. Ambele sunt module de business **specifice unui singur tip de document**
|
||||
(`pack_facturare.ntip`), nu cai generale:
|
||||
|
||||
- **`REST_NOTE_PLATA`** — modul restaurant (`PACK_RESTAURANT`, `nTipFacturaRestaurant`). La
|
||||
stergerea unei vanzari de tip restaurant, `sterge_factura` cheama deja
|
||||
`pack_restaurant.sterge_vanzare(V_ID_VANZARE, V_ID_UTIL)` (`ff_...:5593`, in `CASE`-ul de la
|
||||
finalul procedurii) — un hook dedicat, separat de logica generala. **Nu s-a gasit un hook simetric
|
||||
„re-leaga la reemitere"** in codul citit (nu era in scop sa se citeasca tot `PACK_RESTAURANT` —
|
||||
pachet separat, mii de linii). Clasificare: **(A) suspecta, needs follow-up dedicat** — afecteaza
|
||||
doar documentele cu `ntip = nTipFacturaRestaurant`, deci doar daca #13 include si acest tip in
|
||||
„regenerabile" (de confirmat in `COMUN\docs\tipuri_documente_facturare.md`, cf. S13).
|
||||
- **`IPS_VOYAGES_VANZARI`** — modul specific schemei `ACN` (`PACK_ACN`, `nTipFacturaACN`), legat de
|
||||
un tabel `IPS_VOYAGES_VANZARI`/`IPS_VOYAGE_MEMBERS_VANZARI`/`IPS_VVOYAGE_MEMBERS` (confirmat in
|
||||
`ris_2024_04_09_01_ACN.sql:258-266`, JOIN pe `vz.id_vanzare = vv.vz_id`) — pare o extensie de
|
||||
business pentru un client specific (calatorii/voiaje), nu parte din suita generica ROA. Acelasi
|
||||
tipar: `sterge_factura` cheama `pack_acn.sterge_vanzare(...)` la stergere (`ff_...:5596-5600`,
|
||||
`execute immediate` conditionat de `pack_migrare.ObjectExist('PACK_ACN')` — pachetul exista doar
|
||||
pe schema ACN). Clasificare: **(A) suspecta, needs follow-up dedicat** — probabil in afara
|
||||
perimetrului #13 (client unic, tip de factura special), dar trebuie confirmat, nu presupus.
|
||||
|
||||
**`VANZARI_CURSURI`** — cursul valutar al documentului. Scris de `pack_facturare.scrie_cursuri(nid_vanzare)`
|
||||
(`rec_cale_vanzari_detalii.md:102`), apelat **in interiorul aceluiasi `scrie_in_vanzari`** care
|
||||
genereaza noul `ID_VANZARE` la reemitere (S9 foloseste exact acest drum, cf. §0-bis). Deci un rand
|
||||
nou `VANZARI_CURSURI` se scrie automat pentru noul `ID_VANZARE`, fara nicio interventie separata.
|
||||
**Clasificare: (B)** — se reface singur, ca parte a drumului normal de emitere, nimic de adaugat.
|
||||
|
||||
**`VANZARI_DETALII`** — liniile facturii. E miezul a ceea ce regenerarea insasi scrie (INSERT direct
|
||||
pe noul `ID_VANZARE`, cf. `rec_cale_vanzari_detalii.md:105-113`) — nu e un consumator "extern" care
|
||||
sa se rupa, e obiectul regenerarii. **Clasificare: (B)**, deja acoperit de proiectarea S9/S10, nu se
|
||||
reface aici.
|
||||
|
||||
### VANZARI_CORESP — cazul netratat inca de plan: documentul editat e EL INSUSI parte a unui lant
|
||||
|
||||
Planul (S11, `plan_13...md:3024-3027`) trateaza `VANZARI_CORESP` ca pe o lista simpla, dar
|
||||
`sterge_factura` (apelata de S9 la pasul de stergere) arata ca situatia are **doua fete diferite**,
|
||||
niciuna simpla:
|
||||
|
||||
**(i) Garda de blocare — editarea unui document care e SURSA pentru altul e azi IMPOSIBILA, nu
|
||||
riscanta.** `sterge_factura` (`ff_...:5450-5494`) verifica, INAINTE de orice stergere:
|
||||
```sql
|
||||
SELECT COUNT(*) INTO V_NR_FACT_RETUR FROM VANZARI_CORESP
|
||||
WHERE STERS=0 AND ID_VANZARE_AVIZ = V_ID_VANZARE AND TIP = 3;
|
||||
IF V_NR_FACT_RETUR > 0 THEN RAISE_APPLICATION_ERROR(-20000, 'Pentru acesta factura s-au emis facturi
|
||||
de retur. Trebuie sa stergeti mai intai factura de retur!'); END IF;
|
||||
```
|
||||
si analog pentru `TIP IN (1,2)` (aviz cu factura/aviz de retur emise pe el) si pentru avizele-sursa
|
||||
ale unei „facturi din aviz" (subquery imbricat, `:5478-5494`). **Consecinta pentru S9**: daca
|
||||
utilizatorul incearca sa editeze (regenereze) o factura care are deja o factura de retur emisa
|
||||
pe ea, sau un aviz care are deja o factura/aviz de retur emis pe el, **pasul de stergere din
|
||||
tranzactia S9 arunca `ORA-20000`, tranzactia face rollback, documentul vechi ramane intact** — nu
|
||||
e coruptie silentioasa, e un esec curat, dar e o **limitare functionala reala**: aceste documente
|
||||
nu pot fi editate prin regenerare deloc, cat timp garda ramane activa (si nu exista niciun motiv
|
||||
funcțional sa fie dezactivata — ar contrazice exact protectia pe care garda o ofera azi). *De
|
||||
verificat de S12*: ca mesajul de eroare Oracle ajunge inteligibil la utilizator prin formularul de
|
||||
editare, nu ca o eroare tehnica generica.
|
||||
|
||||
**(ii) Legaturile in care documentul editat e chiar `ID_VANZARE_FACT`/`ID_VANZARE_AVIZ` — marcate
|
||||
`STERS=1` la stergere, NU remigrate automat, dar potential rescrise de reemitere daca parametrii
|
||||
sunt refurnizati corect.** La stergerea documentului vechi, `sterge_factura` executa neconditionat
|
||||
(`:5582-5585`):
|
||||
```sql
|
||||
UPDATE VANZARI_CORESP SET STERS = V_STERS
|
||||
WHERE ID_VANZARE_FACT = V_ID_VANZARE OR ID_VANZARE_AVIZ = V_ID_VANZARE;
|
||||
```
|
||||
— orice legatura in care documentul vechi apare pe oricare parte devine `STERS=1`. **Nu exista cod
|
||||
care sa recreeze automat legatura pentru noul `ID_VANZARE`.** Dar exista o cale prin care se
|
||||
recreeaza **corect, de la sine**, daca S9 e proiectat sa refoloseasca drumul normal: `finalizeaza_factura`
|
||||
(chemata de reemiterea normala) are un `CASE` pe `pack_facturare.ntip` care scrie din nou
|
||||
corespondenta (`WHEN ntip=4: scrie_corespondente_vanzari(1)`; `WHEN ntip=24:
|
||||
scrie_corespondente_vanzari(2)`; `WHEN ntip IN (8,9): scrie_corespondente_vanzari(3)`,
|
||||
`ff_...:14823-14836`) — **daca** S9 seteaza `pack_facturare.ntip` la aceeasi valoare ca documentul
|
||||
original SI re-populeaza `pack_facturare.clistaid`/`clistaid_avize` cu lista de `id_vanzare` sursa
|
||||
originala **inainte** de reemitere, corespondenta se scrie natural, cu noul `ID_VANZARE_FACT`.
|
||||
**Sursa lista originala e recuperabila** din randurile `VANZARI_CORESP` (acum `STERS=1`) ale
|
||||
documentului vechi, citite INAINTE de stergere in aceeasi tranzactie — un pas suplimentar pe care
|
||||
proiectarea S9 trebuie sa-l includa explicit, nu e „gratis". **Clasificare: (A) trebuie remigrat
|
||||
— dar prin re-derivare + refolosirea mecanismului existent, nu prin UPDATE direct pe `VANZARI_CORESP`**
|
||||
(un UPDATE direct ar trebui sa distinga cu grija cele doua roluri — FACT vs AVIZ — si ar risca sa
|
||||
resusciteze legaturi catre documente inca sterse din alte motive; re-emiterea naturala prin `ntip`+
|
||||
`clistaid` e mai sigura, pentru ca refoloseste exact logica deja validata de emiterea normala).
|
||||
|
||||
### marcheaza_facturat — pentru sursa documentului editat, nu pentru documentul insusi
|
||||
|
||||
`marcheaza_facturat` (`ff_...:15381-15418`) actioneaza pe **sursa** (avizul din care s-a facturat,
|
||||
sau avizul-parinte al unui aviz de retur), nu pe documentul care se editeaza. La stergerea
|
||||
documentului vechi (S9, pasul de stergere), pentru `V_TIP=4`/`V_TIP=24`, `sterge_factura` reseteaza
|
||||
deja `FACTURAT=0`/`ID_UTILFACT=NULL` pe sursa (`:5504-5510`, `:5525-5533`) si marcheaza
|
||||
`VANZARI_CANTITATI.STERS=1` pentru cantitatile consumate (`:5517-5523`, `:5535-5540`) — sursa e deci
|
||||
**eliberata** corect de mecanismul EXISTENT, neschimbat. La reemitere, daca `ntip`/`clistaid_avize`
|
||||
sunt refurnizate corect (acelasi argument ca la VANZARI_CORESP mai sus), `finalizeaza_factura`
|
||||
cheama din nou `scrie_cantitati_vanzari_avize` + `marcheaza_facturat` (`:14825-14827`,`:14831`),
|
||||
**reconsumand** sursa sub noul `ID_VANZARE_FACT`. **Clasificare: (C) — mecanismul deja exista si
|
||||
functioneaza simetric (elibereaza la stergere, reconsuma la scriere), CONDITIONAT de aceeasi
|
||||
cerinta ca la VANZARI_CORESP: S9 trebuie sa refurnizeze `ntip` si `clistaid`/`clistaid_avize`
|
||||
originale la reemitere.** Aceasta e exact conditia pe care S12 o testeaza explicit („sursa eliberata
|
||||
si reconsumata", `plan_13...md:3037`) — S11 confirma DE CE mecanismul poate functiona (nu e nevoie
|
||||
de cod nou pentru asta), dar nu inlocuieste testul S12.
|
||||
|
||||
## 4. DOCUMENTE / ID_DOC — legatura cu ID_VANZARE
|
||||
|
||||
**Confirmat: fara legatura.** `DOCUMENTE(ID_DOC, ID_UTIL, DATAORA, TVA_INCASARE, SERIE_ACT, NRACT,
|
||||
DATAACT, DATAIREG, ID_CTR, ID_SET, STERS, ...)` — INSERT-ul complet citat in
|
||||
`idfact_refolosire_si_documente.md:143-148` (`PACK_CONTAFIN.pck:788-817`, cod activ) nu are nicio
|
||||
coloana `ID_VANZARE`. Cheia `ID_DOC = ID_FACT`, care se PASTREAZA la regenerare (decizie E/F, deja
|
||||
data). Acest raport nu reface analiza — deja acoperita exhaustiv de
|
||||
`docs\cercetare\idfact_refolosire_si_documente.md` (S9, punctele A-C), care stabileste separat
|
||||
conditiile pentru pastrarea `ID_FACT` (variabila noua de sesiune in `SET_IDFACT` + upsert real in
|
||||
`DOCUMENTE`). **Clasificare: (C) — confirmat, fara actiune ceruta de S11.**
|
||||
|
||||
## 5. Borderou eFactura si listari
|
||||
|
||||
**eFactura: cheia e `ID_FACT`, nu `ID_VANZARE` — confirmat cu dovada proprie, independenta.**
|
||||
`EsteInEFactura` (`COMUN\programe\ofacturare_editare.prg`, apelata din `ofacturare_comun.vc2:3764`
|
||||
cu `lnIdFact = crsfacturi.id_fact`) verifica prezenta documentului in `ANAF_EFACTURA` **pe
|
||||
`VANZARI.ID_FACT`**, nu pe `id_vanzare` — confirmat explicit ca eroare corectata in
|
||||
`rec_d42_efactura.md:81-109` (implementarea initiala folosea gresit `id_vanzare`, corectata dupa
|
||||
verificare pe date: `id_fact` si `id_vanzare` sunt spatii de ID complet diferite, 0 coincidente pe
|
||||
142 facturi testate). **Cum `ID_FACT` se pastreaza la regenerare (decizie deja luata), `EsteInEFactura`
|
||||
continua sa functioneze corect pe documentul reemis, fara nicio schimbare.** Aceeasi concluzie se
|
||||
aplica probabil borderoului eFactura insusi (cautarea documentelor de trimis/trimise), dar
|
||||
**construirea borderoului nu a fost citita in acest raport** (in afara scopului — cheia `ID_FACT`
|
||||
fiind deja confirmata stabila, riscul e mic, dar afirmatia stricta „borderoul il gaseste corect"
|
||||
ramane de verificat direct pe cod la implementare, nu doar dedusa). **Clasificare: (C), cu rezerva
|
||||
de verificare directa a borderoului la implementare.**
|
||||
|
||||
**Listari/grid cautare facturi**: cursoarele de grid (`crsFacturi`, `crsFacturiOrd`,
|
||||
`ofacturare_comun.vc2:3982,4134-4145,4983`) se reconstruiesc live din Oracle la fiecare deschidere
|
||||
a formularului de listare, filtrate pe `sters=0` — nu tin niciun `id_vanzare` persistat intre
|
||||
sesiuni. Documentul reemis (nou `id_vanzare`, acelasi `id_fact`/serie/numar) apare normal la
|
||||
urmatoarea reincarcare a gridului. **Singurul risc identificat, nu confirmat ca problema reala**:
|
||||
daca undeva in cod exista un `id_vanzare` **retinut pe termen lung** in afara sesiunii curente a
|
||||
formularului (ex. „ultima factura deschisa", favorite, shortcut) — nu s-a gasit niciun asemenea
|
||||
mecanism in codul citit (`ofacturare_comun.vc2`, `ofacturare.prg`), dar nici nu a fost cautat
|
||||
exhaustiv in tot `ROAFACTURARE` (in afara bugetului acestei cercetari). **Clasificare: (C),
|
||||
neconfirmat ca risc real — verificare suplimentara recomandata daca timpul permite.**
|
||||
|
||||
## 6. Incasari/plati si riscuri finale
|
||||
|
||||
**Cautare directa (negativa, dar utila): niciun tabel de incasari/plati nu are FK declarat pe
|
||||
`VANZARI.ID_VANZARE`.** Interogarea exhaustiva `ALL_CONS_COLUMNS`/`ALL_CONSTRAINTS` pe schema `ACN`
|
||||
(tabelul din §3) e completa pentru FK-uri declarate — `INCASARI`, `PLATI`, `IREG_PARTENERI` nu apar
|
||||
in acea lista. Cautare text suplimentara (`grep -rn "ID_VANZARE"` filtrat pe fisiere cu
|
||||
"incasari"/"plati"/"chitant" in nume, in tot `D:\ROA\DATABASE\SCRIPTURI_CLAR`) — **zero rezultate**.
|
||||
Coerent cu arhitectura deja cunoscuta: incasarile/platile se leaga de facturi prin **`ID_FACT`**
|
||||
(coloana pe `ACT`/`IREG_PARTENERI`, cf. `idfact_refolosire_si_documente.md`), nu prin `ID_VANZARE`
|
||||
— `ID_FACT` se pastreaza la regenerare, deci **incasarile/platile nu sunt afectate structural**.
|
||||
**Clasificare: (C), cu dovada dubla (schema + text), coerenta cu memoria de proiect „zero cazuri in
|
||||
date nu e dovada" — aici insa e absenta de FK + absenta de text, nu absenta de date, deci e o
|
||||
dovada structurala, nu doar un data point.**
|
||||
|
||||
## Rezumat clasificari
|
||||
|
||||
| Consumator | Clasificare | Actiune ceruta |
|
||||
|---|---|---|
|
||||
| `ATASAMENTE_VANZARI.ID_VANZARE` | **(A)** | `UPDATE ... SET id_vanzare=:nou WHERE id_vanzare=:vechi AND sters=0`, in tranzactia S9 |
|
||||
| `VANZARI_CORESP` (documentul editat ca FACT/AVIZ al altui lant) | **(A)**, prin re-emitere naturala | S9 trebuie sa citeasca `ntip`+lista sursa originala INAINTE de stergere si sa le refurnizeze la reemitere |
|
||||
| `marcheaza_facturat`/`VANZARI_CANTITATI` (sursa documentului editat) | **(C)**, conditionat | functioneaza automat DACA `ntip`/`clistaid` sunt refurnizate (acelasi mecanism ca mai sus) |
|
||||
| `VANZARI_CURSURI` | **(B)** | se rescrie automat de `scrie_in_vanzari`, nimic de facut |
|
||||
| `VANZARI_DETALII` | **(B)** | e obiectul regenerarii, acoperit de S9/S10 |
|
||||
| `DOCUMENTE`/`ID_DOC` | **(C)** | confirmat fara legatura, acoperit de `idfact_refolosire_si_documente.md` |
|
||||
| eFactura (`EsteInEFactura`, `ANAF_EFACTURA`) | **(C)** | cheie `ID_FACT`, pastrat — verificare directa a borderoului recomandata la implementare |
|
||||
| Listari/grid facturi | **(C)** | cursoare live, fara stare persistata gasita |
|
||||
| Incasari/plati | **(C)** | fara FK, fara referinta text — cheie e `ID_FACT` |
|
||||
| `REST_NOTE_PLATA` (modul restaurant) | **(A)** suspecta | needs follow-up dedicat, `PACK_RESTAURANT` necitit integral |
|
||||
| `IPS_VOYAGES_VANZARI` (modul ACN specific client) | **(A)** suspecta | needs follow-up dedicat, `PACK_ACN` necitit integral, posibil in afara perimetrului #13 |
|
||||
|
||||
## Riscuri si ce ramane de decis de Marius
|
||||
|
||||
1. **Editarea documentelor care sunt sursa unui lant retur/aviz e azi BLOCATA, nu doar riscanta.**
|
||||
`sterge_factura` arunca `ORA-20000` daca documentul editat are deja facturi/avize de retur emise
|
||||
pe el (§3.i). Nu e o eroare de proiectare — garda protejeaza integritatea lantului — dar
|
||||
inseamna ca „editare prin regenerare" **nu va functiona deloc** pentru aceste documente, cat timp
|
||||
utilizatorul nu sterge intai documentele-copil. **De decis**: acceptat ca limitare (cu mesaj
|
||||
clar in UI, tradus din eroarea Oracle), sau se doreste alt comportament?
|
||||
2. **Recuperarea `VANZARI_CORESP`/`marcheaza_facturat` la reemitere depinde de un pas pe care planul
|
||||
nu-l mentioneaza explicit**: citirea listei sursa originale (`ID_VANZARE_AVIZ`/`ID_VANZARE_FACT`
|
||||
din randurile `VANZARI_CORESP` ale documentului vechi) **inainte** de stergere, si refurnizarea
|
||||
ei ca `pack_facturare.clistaid`/`clistaid_avize` la reemitere. Fara acest pas, corespondenta si
|
||||
`marcheaza_facturat` **nu se scriu deloc** pentru documentul reemis (nu eroare, ci scriere lipsa
|
||||
silentioasa) — de adaugat explicit in proiectarea S9, nu presupus „vine gratis" din drumul normal.
|
||||
3. **`ATASAMENTE_VANZARI` nu e „date de utilizator pierdute"**, cf. §2 — e un audit trail de
|
||||
PDF-uri auto-generate la listare. Remigrarea (`UPDATE ... SET id_vanzare`) e simpla si ieftina,
|
||||
dar motivatia corecta pentru Marius e „pastrarea istoricului tiparit", nu „pierdere de date
|
||||
introduse manual" — poate schimba prioritatea relativa fata de alte itemi din S11/S12.
|
||||
4. **`REST_NOTE_PLATA` si `IPS_VOYAGES_VANZARI`** raman needs-investigation — descoperite prin FK,
|
||||
nu prin text, deci nu erau in raza cautarilor anterioare (nici in planul S11 original). Daca
|
||||
tipurile de document asociate (`nTipFacturaRestaurant`, `nTipFacturaACN`) nu sunt in domeniul
|
||||
„regenerabile" al #13 (de confirmat in `tipuri_documente_facturare.md`, S13), riscul dispare de
|
||||
la sine — dar asta trebuie confirmat, nu presupus.
|
||||
5. **Borderoul eFactura insusi** (nu doar `EsteInEFactura`) nu a fost citit direct — cheia `ID_FACT`
|
||||
fiind stabila, riscul e mic, dar afirmatia din *Gata cand* a S11 („documentul reemis se regaseste
|
||||
corect in borderoul eFactura") merita o verificare punctuala pe cod la implementare, nu doar
|
||||
dedusa din `EsteInEFactura`.
|
||||
|
||||
## STARE / CE RAMANE
|
||||
|
||||
**Cercetare incheiata pentru toate cele 7 puncte cerute de briefing**, cu dovada `fisier:linie` pe
|
||||
fiecare afirmatie portanta: inventar exhaustiv (via FK Oracle + cod), `ATASAMENTE_VANZARI` (sectiune
|
||||
proprie, cu corectia premisei „date de utilizator"), `marcheaza_facturat`/`VANZARI.FACTURAT`/
|
||||
`VANZARI_CANTITATI`, `DOCUMENTE`/`ID_DOC` (confirmat, fara redeschidere), borderou eFactura si
|
||||
listari, riscuri finale.
|
||||
|
||||
**Constatare centrala**: mecanismul S9 planificat (regenerare cu `ID_VANZARE` nou, pe drumul normal
|
||||
de emitere) e **diferit** de mecanismul #6 deja livrat (`actualizeaza_vanzari`, reutilizeaza acelasi
|
||||
`ID_VANZARE`, doar `COD` se schimba) — deci nicio protectie construita pentru #6 nu se mosteneste
|
||||
automat de S9. Singurul consumator care chiar are nevoie de un `UPDATE` explicit nou este
|
||||
`ATASAMENTE_VANZARI` (clasificare A simpla). `VANZARI_CORESP`/`marcheaza_facturat` se pot rezolva
|
||||
**fara cod PL/SQL nou**, refolosind mecanismul existent (`finalizeaza_factura`'s `CASE` pe `ntip`),
|
||||
**daca** S9 e proiectat sa citeasca si sa refurnizeze parametrii sursei originale — asta e o cerinta
|
||||
de design care trebuie scrisa explicit in S9, nu deductibila implicit.
|
||||
|
||||
**Neverificat, ramas pentru follow-up** (declarat, nu ascuns): corpul complet al `PACK_RESTAURANT`/
|
||||
`PACK_ACN` (pentru `REST_NOTE_PLATA`/`IPS_VOYAGES_VANZARI`), borderoul eFactura citit direct (nu doar
|
||||
`EsteInEFactura`), o cautare exhaustiva de „id_vanzare retinut pe termen lung" in tot codul VFP
|
||||
(favorite/shortcut-uri), si confirmarea in `tipuri_documente_facturare.md` a caror tipuri intra
|
||||
efectiv in perimetrul „regenerabile" al #13 (ar elimina sau confirma riscurile 1 si 4).
|
||||
|
||||
Zero modificari de cod, zero write-back, zero `git_sync.ps1`/`txt2vcx.ps1`, zero commit. Pe Oracle
|
||||
doar `SELECT` (schema `MARIUSM_AUTO`, interogari pe `ALL_TAB_COLUMNS`/`ALL_CONSTRAINTS`/`ALL_VIEWS`/
|
||||
`ALL_SOURCE`, plus `SELECT` de numarare pe date de test — niciuna cu efect de scriere).
|
||||
318
docs/cercetare/s2_factureaza_unificare.md
Normal file
318
docs/cercetare/s2_factureaza_unificare.md
Normal file
@@ -0,0 +1,318 @@
|
||||
# S2 — Proiectare: o singura procedura `factureaza`
|
||||
|
||||
Cercetare read-only pentru povestea **S2** din `docs\plan_13_unificare_formular_facturare.md`.
|
||||
Niciun fisier de cod atins, `git_sync.ps1` nerulat, niciun write-back, niciun commit.
|
||||
|
||||
## Verdict
|
||||
|
||||
**Se poate unifica, si e mai simplu decat pare.** `factureaza2` nu e o a doua implementare
|
||||
funcţională care trebuie fuzionata cu grija — e un fork din 08.06.2017 la care s-a **dezactivat
|
||||
execuţia interogarii de articole** (`lnSucces = 1` hardcodat, `goExecutor.oExecute` niciodata
|
||||
apelat) si mai multe blocuri intregi sunt inchise cu `If .F.`. Are **exact un singur apelant** in
|
||||
tot codul (`ofacturare.prg:90`, din interiorul lui `factureaza` insusi, in spatele unui
|
||||
`AMESSAGEBOX` de confirmare) si **zero utilizatori reali** dincolo de acel switch de test. Riscul de
|
||||
regresie e mic pentru ca nu exista nimic funcţional de pierdut in `factureaza2` — obstacolul
|
||||
principal nu e tehnic, e ca formularul spre care duce (`frm_facturare_articole2`) e tot un prototip
|
||||
neterminat (`Init` de 92 de linii, nu completeaza antetul), deci unificarea proceduri lor **nu**
|
||||
face formularul nou utilizabil — doar elimina duplicarea de cod. Asta ramane treaba lui S3.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventarul celor doua proceduri
|
||||
|
||||
Ambele sunt proceduri globale (nu metode de clasa) in **`COMUN\programe\ofacturare.prg`**, incarcat
|
||||
via `SET PROCEDURE ... ADDITIVE` din `Programe\roafacturare.prg`. Nu exista omonime — verificat cu
|
||||
`Get-ChildItem -Recurse *.prg | Select-String '^\s*(PROCEDURE|FUNCTION)\s+factureaza2?\b'` pe tot
|
||||
`ROAFACTURARE` si cu Grep pe `.vc2` din `COMUN`: un singur rezultat pentru fiecare nume.
|
||||
|
||||
| | `factureaza` | `factureaza2` |
|
||||
|---|---|---|
|
||||
| Locatie | `COMUN\programe\ofacturare.prg:81-577` (497 linii) | `COMUN\programe\ofacturare.prg:583-1081` (499 linii) |
|
||||
| Parametri | `LPARAMETERS tnTip, toFactura` (`:82`) — `toFactura` = obiect, pentru copiere/modificare | `LPARAMETERS tnTip` (`:584`) — **fara** al doilea parametru |
|
||||
| Apelanti | ~20+ puncte de intrare reale (sectiunea 3) | **unul singur**: `ofacturare.prg:90`, din `factureaza` |
|
||||
|
||||
`ofacturare.prg` in sine e cod comun al **intregii suite**: exista o copie identica-la-origine in
|
||||
`COMUN\programe\ofacturare.prg` a fiecarui produs ROA verificat (ROACONT, ROAGEST, ROACONTRACTE,
|
||||
ROAAUTO, ROAACNPRO, ROAIMOB, plus alte ~30 produse) — fiecare cu propriile `factureaza`/`factureaza2`
|
||||
la linii apropiate (de regula `:76-77` sau `:87-88` pentru comutatorul `gnFacturareNou`, dupa
|
||||
versiune). Nu exista in `COMUNROA` (biblioteca partajata separata) — e sincronizat manual intre
|
||||
produse ca fisier COMUN, nu ca livrabil COMUNROA.
|
||||
|
||||
## 2. Diff-ul semantic
|
||||
|
||||
Comparatie linie-cu-linie, cu citate `ofacturare.prg:linie`. **Sase din cele noua diferente listate
|
||||
in sectiunea A a planului se confirma exact; una e imprecisa; una lipseste din plan.**
|
||||
|
||||
### Ce face `factureaza` si nu face `factureaza2`
|
||||
|
||||
| Ce | `factureaza` | `factureaza2` |
|
||||
|---|---|---|
|
||||
| Tipurile 51 (ROAACNPRO), 52 (contract factura fiscala valuta) rutate la FACTURA | `Inlist(tnTip,45,48,49,**51,52**)` la `:192` si `:222` | `Inlist(tnTip,45,48,49)` la `:674` si `:700` — **51/52 lipsesc**, ar cadea in `Otherwise` -> AVIZ |
|
||||
| Ramura `llCopiere`/`toFactura` (copiere/modificare factura) | declarata `:102`, populata `:111`, folosita la completarea datelor (`:200-206`), la construirea interogarii (`Case m.llCopiere` -> `cursor_retur_document`, `:267-268`), si la adaugarea articolelor din lista de preturi peste cele copiate (`:454-474`) | **absenta complet** — nicio declarare a `llCopiere`, niciun tratament al lui `toFactura` |
|
||||
| Deschiderea `frm_date_factura`/`frm_date_aviz` | activa, cu `toFactura` transmis constructorului (`:229-235`) | inchisa cu `If .F.` (`:707-711`) |
|
||||
| Filtrul `RORTC` pe `jtva_coloane` | `AND !'RORTC'$UPPER(coloana_jv)` la ambele ramuri (`:243`, `:245`) | absent (`:715`, `:717`) |
|
||||
| Executia interogarii de articole | `lnSucces = goExecutor.oExecute(lcSqlCursor, lcCursor)` (`:311`) | **inchisa cu `If .F.`** (`:823-826`); imediat dupa, `lnSucces = 1` hardcodat (`:827`) — cursorul SQL construit la `:748-822` nu ruleaza niciodata |
|
||||
| Verificarea "nu exista articole" | activa (`:324-328`) | **inchisa cu `If .F.`** (`:838`) — combinata cu `lnSucces=1` de mai sus, ramura merge mereu pe calea "am articole", indiferent de continut |
|
||||
| Zeroizarea `gestionabil` pe proforma | `IF poDate.eProforma = 1 ... UPDATE (lcCursor) SET gestionabil = 0` (`:333-336`) | **absenta** — `eProforma` nu apare nicaieri in `factureaza2` (verificat cu grep pe tot fisierul) |
|
||||
| `crspolitici` / `crscontracte` (populare combo-uri) | necondiţionate (`:420-440`) | **ambele inchise cu `If .F.`** (`:956-968`, `:969-980`) |
|
||||
| `GetInstitutiePublica` | `poDate.institutie_publica = GetInstitutiePublica(...)` (`:509`) | **absenta** |
|
||||
| `codnc8`/`codcpv` pe fiecare linie | `Replace codnc8 WITH ..., codcpv WITH ...` in `Scan` (`:516-517`), pe langa `codmatc` | **doar `codmatc`** (`:1027`) — `codnc8`/`codcpv` lipsesc |
|
||||
| `GetSoldClient` + `poDate.sold_lei`/`sold_valuta` inainte de listare | prezent, in ramura non-bon-fiscal (`:530-533`) | **absent** — ramura echivalenta (`:1040-1041`) sare direct la `listeaza_ofacturare()` |
|
||||
| Formularul de articole | `frm_facturare_articole` (`:395`) | `frm_facturare_articole2` (`:929`) — **singura diferenta functionala reala** care justifica existenta lui `factureaza2` |
|
||||
|
||||
### Corectii fata de sectiunea A a planului
|
||||
|
||||
- **"`verifica_numar(16, ...)` pentru chitanta" — planul GRESESTE.** E prezent **identic** in ambele:
|
||||
`factureaza:502-504` si `factureaza2:1018-1020` (`If poDate.incasat <> 0 ...
|
||||
poGeneratorNumere.verifica_numar(16, poDate.nr_incasare) Endif`). Nu lipseste din `factureaza2`.
|
||||
- **"`cursor_avize` cu tip 23" — planul e imprecis.** Nu exista nicio asociere `cursor_avize`/tip 23
|
||||
in niciuna din cele doua proceduri (`cursor_avize` trateaza doar `tnTip = 4`, identic in ambele,
|
||||
`:294-295` / `:774-775`). Ce difera real pentru tipul 23: in `factureaza`, tipul 23 e prins in
|
||||
`Case Inlist(tnTip,1,22,5,29,7,10,**23**)` -> `cursor_preturi` (`:279-282`); in `factureaza2`, 23
|
||||
**nu** e in acea lista (`:758`, doar `1,22,5,29,7,10`) si cade in schimb in
|
||||
`Case Inlist(tnTip,**23**,41)` -> `cursor_gestiune` (`:776-778`). E o rutare complet diferita
|
||||
pentru acelasi tip de document, nu o simpla lipsa — **de adaugat explicit la lista din sectiunea A**.
|
||||
- **Nementionat in plan — tipul 52 lipseste si din `Do Case` de contract**: `factureaza`
|
||||
`Case Inlist(tnTip,2,26,6,**52**)` (`:283`) vs `factureaza2` `Case Inlist(tnTip,2,26,6)` (`:762`) —
|
||||
aceeasi lipsa a tipului 52 ca la nIdTipDoc, dar intr-un loc separat din cod; **tipul 24** (aviz de
|
||||
retur) lipseste similar din `Case Inlist(tnTip,8,9,**24**)` (`:306`) vs `Case Inlist(tnTip,8,9)`
|
||||
(`:819`).
|
||||
|
||||
### Ce fac amandoua identic (verificat, nu presupus)
|
||||
|
||||
Structura comentariilor `lnIdSet`/`pnTipFacturare`, bucla `Do While lnRaspuns = 6`, apelul
|
||||
`actualizeaza_optiuni_program()`, crearea `poDate`/`poGeneratorNumere`, alegerea `frm_date_factura` /
|
||||
`frm_date_aviz_lucrare` / `frm_date_aviz` pentru antet (desi in `factureaza2` deschiderea propriu-zisa
|
||||
e dezactivata), tratarea `tnTip=30` (aviz din NIR, inclusiv bucla `Scatter`/`Insert Into crsfactura`
|
||||
identica caracter-cu-caracter), `verifica_numar(16,...)`, dezalocarea celor trei numere
|
||||
(`nIdTipDoc`, 16, 3/26) la abandon, si intreaga bucla finala de `AMESSAGEBOX`
|
||||
"Doriti sa continuati" + `CloneObj`/`poDate.Reset()`.
|
||||
|
||||
### Ce face `factureaza2` si nu face `factureaza`
|
||||
|
||||
Un singur lucru: verificari defensive suplimentare de tip la iesirea timpurie (`pnButon = 2`):
|
||||
```
|
||||
IF TYPE('poDate.nr_incasare') = 'N'
|
||||
poDate.nr_incasare = 0
|
||||
ENDIF
|
||||
IF TYPE('poDate.incasat') = 'N'
|
||||
poDate.incasat = 0
|
||||
ENDIF
|
||||
```
|
||||
(`factureaza2:727-732`) — `factureaza` face resetarea echivalenta necondiţionat, dar mai tarziu, in
|
||||
ramura de succes (`:546-547`), nu pe calea de abandon. Diferenta e cosmetica, nu comportamentala pe
|
||||
fluxul normal.
|
||||
|
||||
## 3. Toti apelantii
|
||||
|
||||
**Cautare in tot arborele `D:\ROA`** (fiecare produs + `COMUN`-ul lui + `COMUNROA`), cu capcana
|
||||
"Grep nu vede COMUN" tratata explicit (path dat direct pe fiecare `COMUN`, nu doar pe radacina
|
||||
produsului — altfel cautarea sare tacut peste el, per gitignore-ul fiecarui produs).
|
||||
|
||||
**Niciun produs verificat (ROACONT, ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB) nu apeleaza `factureaza`
|
||||
direct din cod propriu — nici in `COMUN`, nici in partea specifica produsului.** Singura urma in
|
||||
`COMUN`-urile lor e propria copie a `ofacturare.prg`/`oproceduri_facturare.prg` (definitia, nu un
|
||||
apel). Confirma independent observatia din `coresp_cont_venchelt.md` ca ROAACNPRO nu trece prin acest
|
||||
flux (foloseste `pack_acn.salveaza_regdoc`, nu `contabilizeaza_articol`). **Exceptia: ROACONTRACTE**,
|
||||
care are un apel real, specific produsului, la wrapper-ul `facturare_contracte`.
|
||||
|
||||
### Apelanti directi ai `factureaza(N)`
|
||||
|
||||
| Apelant | Produs | Parametri | Tip document |
|
||||
|---|---|---|---|
|
||||
| `Meniuri\politica.mn2:12` | ROAFACTURARE | `factureaza(1)` | lista de preturi, lei |
|
||||
| `Meniuri\politica.mn2:15` | ROAFACTURARE | `factureaza(8)` | retur factura lei |
|
||||
| `Meniuri\politica.mn2:18` | ROAFACTURARE | `factureaza(45)` | restaurant |
|
||||
| `Meniuri\politica.mn2:26` | ROAFACTURARE | `factureaza(49)` | marfa custodie fara descarcare K |
|
||||
| `Meniuri\politica.mn2:29` | ROAFACTURARE | `factureaza(48)` | marfa custodie cu descarcare K |
|
||||
| `Meniuri\politica.mn2:37` | ROAFACTURARE | `factureaza(5)` | lista de preturi, valuta (invoice) |
|
||||
| `Meniuri\politica.mn2:40` | ROAFACTURARE | `factureaza(7)` | credit note |
|
||||
| `Meniuri\politica.mn2:43` | ROAFACTURARE | `factureaza(10)` | factura fiscala valuta |
|
||||
| `Meniuri\politica.mn2:46` | ROAFACTURARE | `factureaza(9)` | retur factura valuta |
|
||||
| `Meniuri\contracte.mn2:12` | ROAFACTURARE | `factureaza(2)` | contract, lei |
|
||||
| `Meniuri\contracte.mn2:15` | ROAFACTURARE | `factureaza(6)` | contract, valuta |
|
||||
| `Meniuri\contracte.mn2:18` | ROAFACTURARE | `factureaza(52)` | contract, factura fiscala valuta |
|
||||
|
||||
### Apelanti prin `COMUN\programe\oproceduri_facturare.prg` (dispecer comun, un wrapper per grup de tipuri)
|
||||
|
||||
| Wrapper (`oproceduri_facturare.prg`) | -> `factureaza(N)` | Apelat din |
|
||||
|---|---|---|
|
||||
| `facturare_contracte(tcTip)` (`:119-136`) | `factureaza(2)`/`factureaza(6)`/`factureaza(52)` dupa `tcTip` | `ROAFACTURARE\Clase\ofundal_facturare.vc2:890-894`, `ROAFACTURARE\Ferestre\fundal.sc2:924`, **`ROACONTRACTE\Clase\ferestre_contracte.vc2:1542-1546`** (produs diferit, apel real) |
|
||||
| `facturare_comenzi()` (`:139-141`) | `factureaza(3)` | `COMUN\clase\ocomenzi.vc2:1580-1596`, metoda `do_factura` — `SCATTER NAME goComanda MEMO` inainte de apel |
|
||||
| `facturare_avize()` (`:144-146`) | `factureaza(4)` | `ROAFACTURARE\Clase\ofundal_facturare.vc2:905`, `ROAFACTURARE\Ferestre\fundal.sc2:932` |
|
||||
| `copiere_factura(toFactura)` (`:150-153`) | `factureaza(toFactura.Tip, toFactura)` | `COMUN\clase\ofacturare_comun.vc2:3710` — fisierul de perimetrul #6, doar citit, neatins |
|
||||
| `facturare_lista_de_preturi()` (`:114-116`) | `Do politica.mpr` -> popup-ul din `politica.mn2` (vezi tabelul de mai sus) | `ROAFACTURARE\Clase\ofundal_facturare.vc2:883`, `ROAFACTURARE\Ferestre\fundal.sc2:920` |
|
||||
| `emitere_aviz_clienti(tnTip)` (`:198-228`) | `factureaza(21/22/26/24)` dupa `tnTip=1/2/3/7`; `tnTip=4,5,6` -> `initializeaza_vanzare_din_stoc`, nu `factureaza` | `ROAFACTURARE\Meniuri\aviz_clienti.mn2:21,47,51,55,59,63,67` |
|
||||
| `emitere_aviz_clienti_debitori(tnTip)` (`:230-243`) | `factureaza(28/29)` dupa `tnTip=1/2` | `ROAFACTURARE\Meniuri\aviz_clienti_debitori.mn2:22,26` |
|
||||
| `emitere_aviz_clienti_custodie(tnTip)` (`:244-259`) | `factureaza(42/47)` dupa `tnTip=1/2`; `tnTip=3` -> `initializeaza_vanzare_din_stoc` | `ROAFACTURARE\Meniuri\aviz_clienti_custodie.mn2:30,34,38` |
|
||||
| `emitere_aviz_transfer(tnTip)` (`:261-280`) | `factureaza(25/23/27/30/41)` dupa `tnTip=1..5` | `ROAFACTURARE\Meniuri\aviz_subunitati.mn2:36,40,44,48,52` |
|
||||
|
||||
**Total: ~33 puncte de intrare reale in cod pentru `factureaza`, doar unul pentru `factureaza2`**
|
||||
(`ofacturare.prg:90`, in interiorul lui `factureaza`, gatit de `gnFacturareNou=1` + confirmare DA la
|
||||
`AMESSAGEBOX`).
|
||||
|
||||
## 4. Rolul lui `gnFacturareNou`
|
||||
|
||||
**Nu e un comutator de productie — e un switch de dezvoltator, fara nicio persistenta.**
|
||||
|
||||
- **Declarat:** nicaieri. Cautare exhaustiva (`gnFacturareNou`, tot `D:\ROA`, extensii
|
||||
`.prg/.vc2/.sc2/.mn2`) — singura aparitie e exact linia care il citeste (`ofacturare.prg:88`, sau
|
||||
liniile echivalente in fiecare copie de produs). Nu exista intr-un ecran de optiuni, nu e coloana
|
||||
intr-un tabel de configurare (spre deosebire de `gnScadereStoc`, `gl406` etc., citite din
|
||||
`oinit_optiuni.prg`).
|
||||
- **Citit:** o singura data, `ofacturare.prg:88` — `If Type('gnFacturareNou') = 'N' And
|
||||
m.gnFacturareNou = 1`.
|
||||
- **Valoare implicita:** inexistenta ca variabila -> `Type()` intoarce `'U'` (Undefined), deci
|
||||
conditia e falsa implicit. Devine `.T.` doar daca cineva a facut manual, in sesiunea VFP curenta
|
||||
(de regula din fereastra de comenzi a IDE-ului), `gnFacturareNou = 1`.
|
||||
- **Cine il seteaza:** nimeni, in cod. E gandit sa fie setat manual de un dezvoltator care vrea sa
|
||||
testeze prototipul.
|
||||
- **Confirma sau infirma ca e comutator intre proceduri, nu intre formulare?** Azi, chiar intre
|
||||
**proceduri** — `factureaza:88-93` face `Return factureaza2(m.tnTip)` dupa raspunsul DA la dialog,
|
||||
adica sare complet in cealalta procedura, cu tot ce lipseste din ea (sectiunea 2). Planul (S2,
|
||||
linia 1423) vrea sa-l transforme in comutator **intre formulare**, in interiorul unei singure
|
||||
proceduri — schimbare corecta, pentru ca azi dubleaza cod, nu doar UI.
|
||||
|
||||
## 5. Proiectarea unificarii
|
||||
|
||||
### Semnatura
|
||||
|
||||
**Neschimbata la aceasta poveste:** `factureaza(tnTip, toFactura)`. Parametrul de sursa explicit
|
||||
(comanda/contract) e treaba lui **S3c**, care depinde de S2 si il trateaza separat — nu se amesteca
|
||||
aici (planul insusi spune "Atinge cod comun intregii suite — nu se face impreuna cu alta
|
||||
modificare").
|
||||
|
||||
### Ramificarea interna
|
||||
|
||||
Nu e nevoie de o rescriere — `factureaza` de azi e deja versiunea completa si functionala. Interventia
|
||||
e chirurgicala:
|
||||
|
||||
1. **Muta verificarea `gnFacturareNou`** de la inceputul procedurii (azi `:88-93`, unde face
|
||||
`Return factureaza2(...)`) la **punctul unde se alege `lcObject`** pentru formularul de articole
|
||||
(azi `:395` in `factureaza`, ramura corespunzatoare celei din `factureaza2:929`):
|
||||
```
|
||||
Case tnTip = 27
|
||||
lcObject = [frm_avizare_lucrare]
|
||||
Otherwise
|
||||
lcObject = IIF(m.llFacturareNoua, [frm_facturare_articole2], [frm_facturare_articole])
|
||||
Endcase
|
||||
```
|
||||
unde `llFacturareNoua` e calculat **o singura data**, la fel ca azi (`Type('gnFacturareNou')='N'
|
||||
And gnFacturareNou=1`, urmat de acelasi `AMESSAGEBOX` DA/NU), dar **fara** `Return` in alta
|
||||
procedura — doar seteaza flagul si continua executia normala.
|
||||
2. **Tot restul ramane identic** — cursorul de articole, `eProforma`, `codmatc`/`codnc8`/`codcpv`,
|
||||
`GetInstitutiePublica`, `GetSoldClient`, filtrul `RORTC`, rutarea tipurilor 51/52/23/24, ramura
|
||||
`llCopiere`/`toFactura` — pentru ca acestea nu au nicio legatura cu care formular de articole se
|
||||
deschide. `factureaza2` nu avea o varianta "corecta, dar diferita" a acestor blocuri — pur si
|
||||
simplu nu le avea, pentru ca fusesera dezactivate in graba la prototipare (`If .F.` peste tot),
|
||||
nu pentru ca noul formular ar avea nevoie de alt comportament acolo.
|
||||
3. **Sterge `factureaza2`** in intregime (`:583-1082`, inclusiv bannerul `INCEPUT`/`SFARSIT`).
|
||||
|
||||
### Ordinea, ca suita sa nu fie stricata niciun moment
|
||||
|
||||
Riscul de "moment in care suita e stricata" e mic aici, spre deosebire de S3c — pentru ca
|
||||
`factureaza2` **nu are alt apelant decat el insusi prin `factureaza`**. Ordinea propusa:
|
||||
|
||||
1. Muta logica de selectie a lui `lcObject` (pasul 1 de mai sus) **inainte** de a sterge
|
||||
`factureaza2` — la acest punct, codul are temporar ambele cai active (comutatorul nou +
|
||||
`factureaza2` inca existent, dar orfan). Se poate testa manual ca `gnFacturareNou=1` deschide
|
||||
`frm_facturare_articole2` din interiorul lui `factureaza`, cu restul comportamentului corect
|
||||
(spre deosebire de azi, unde deschiderea prin `factureaza2` sare peste `eProforma`, `codnc8` etc.)
|
||||
2. Abia dupa verificare, **sterge `factureaza2`** — pasul e sigur pentru ca nu mai are niciun apelant
|
||||
(linia 90, singurul, a fost deja inlocuita la pasul 1).
|
||||
3. **Un singur fisier se schimba**: `COMUN\programe\ofacturare.prg`. Niciun apelant din tabelul de
|
||||
la punctul 3 nu are nevoie de nicio modificare — toti cheama `factureaza(N)` cu aceeasi semnatura,
|
||||
niciunul nu cheama `factureaza2` direct.
|
||||
4. **ROACONTRACTE nu necesita nicio schimbare** la aceasta poveste — apelul lui la
|
||||
`facturare_contracte` -> `factureaza(2/6/52)` nu atinge `factureaza2` deloc.
|
||||
|
||||
## 6. Riscurile de regresie
|
||||
|
||||
- **Cel mai probabil sa se rupa:** ramura `llCopiere`/`toFactura`, pentru ca e cea mai complexa
|
||||
bucata de logica prezenta doar in `factureaza` (`cursor_retur_document`, apoi suprapunerea cu
|
||||
`cursor_preturi` pentru a permite adaugarea de articole noi peste cele copiate, `:454-474`). Nu
|
||||
are nicio acoperire azi in `factureaza2` de comparat — deci orice greseala de copy-paste la mutarea
|
||||
liniei `lcObject` risca sa rupa exact aceasta ramura, nu partea "noua". **Testat manual din
|
||||
`copiere_factura` (`ofacturare_comun.vc2:3710`) inainte si dupa.**
|
||||
- **Al doilea cel mai probabil:** rutarea tipurilor 23/24/51/52, pentru ca azi cad in `Do Case`-uri
|
||||
diferite intre cele doua proceduri (sectiunea 2) — o gresala de aliniere la stergerea lui
|
||||
`factureaza2` nu ar afecta functional (pentru ca acele Do Case-uri raman neatinse, doar duplicate
|
||||
disparute), dar merita o trecere vizuala pe fiecare tip listat la punctul 3 dupa modificare.
|
||||
- **ROACONTRACTE** — singurul apelant real din alt produs. **Nu poate fi testat din arborele de lucru
|
||||
ROAFACTURARE** — cere un build/rulare separata a ROACONTRACTE, cu propriul `ofacturare.prg`
|
||||
sincronizat manual (nu e acelasi fisier fizic, e o copie). Verificarea "gata" trebuie sa includa
|
||||
explicit macar un test manual pe ROACONTRACTE, tip contract-factura, nu doar pe ROAFACTURARE.
|
||||
- **`frm_facturare_articole2` insusi ramane netestabil functional** la aceasta poveste — `Init`-ul
|
||||
lui nu completeaza antetul (S1), deci deschiderea cu `gnFacturareNou=1` va arata un formular cu
|
||||
campuri goale la fel ca azi. Nu e o regresie introdusa de S2, dar e o asteptare de gestionat: S2
|
||||
**nu** face `frm_facturare_articole2` utilizabil, doar elimina procedura duplicata din spatele lui.
|
||||
- **Nimic din asta e testabil headless** — toate cele ~33 puncte de intrare sunt declansate din
|
||||
meniuri/formulare UI (`ON SELECTION BAR`, `DO ... IN ...` din metode de clic), iar rezultatul se
|
||||
verifica prin `ACT`/`VANZARI`/`RUL` in Oracle sau vizual pe formular. Testarea de dupa modificare
|
||||
trebuie sa fie manuala, minim pe: o factura lista de preturi lei (tip 1), un aviz din comanda (tip
|
||||
21), o factura din contract lei (tip 2, din `ROACONTRACTE` daca fezabil), o copiere de factura
|
||||
(`toFactura` populat), si — cu `gnFacturareNou=1` setat manual — confirmarea ca
|
||||
`frm_facturare_articole2` se deschide fara eroare (nu neaparat ca arata corect, asta e S3).
|
||||
|
||||
## 7. Criteriul de "gata", rescris si verificabil
|
||||
|
||||
Planul spune azi: *"`factureaza2` nu mai exista, iar comutatorul alege formularul, nu procedura."*
|
||||
Rescris, verificabil fara ambiguitate:
|
||||
|
||||
1. **Structural** (verificabil cu o comanda, fara sa citesti codul):
|
||||
```
|
||||
Get-ChildItem -Recurse 'D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare.prg' |
|
||||
Select-String -Pattern '^\s*(PROCEDURE|FUNCTION)\s+factureaza2\b'
|
||||
```
|
||||
trebuie sa intoarca **zero rezultate**.
|
||||
```
|
||||
Select-String -Path 'D:\ROA\ROAFACTURARE\COMUN\programe\ofacturare.prg' -Pattern 'gnFacturareNou'
|
||||
```
|
||||
trebuie sa intoarca **exact o aparitie**, situata in interiorul lui `factureaza`, la punctul unde
|
||||
se alege `lcObject` (nu inainte de constructia lui `poDate`, ca azi).
|
||||
2. **Comportamental**, testat manual, fara `gnFacturareNou` setat (calea implicita, majoritatea
|
||||
utilizatorilor): cele patru documente listate la sectiunea 6 (factura lista de preturi, aviz din
|
||||
comanda, factura din contract, copiere factura) produc aceleasi randuri in `ACT`/`VANZARI`/`RUL`
|
||||
ca inainte de modificare.
|
||||
3. **Comportamental**, cu `gnFacturareNou = 1` setat manual si DA la dialog: se deschide
|
||||
`frm_facturare_articole2` (nu `frm_facturare_articole`), fara eroare la deschidere, cu
|
||||
`eProforma`/`codnc8`/`codcpv`/`GetInstitutiePublica`/`GetSoldClient`/filtrul RORTC/rutarea
|
||||
51-52-23-24 toate active identic cu calea implicita (spre deosebire de azi, cand
|
||||
`gnFacturareNou=1` le sarea pe toate).
|
||||
4. **ROACONTRACTE**: minim un test manual de facturare din contract, dupa sincronizarea
|
||||
`ofacturare.prg` in acel produs, confirma acelasi rezultat ca inainte.
|
||||
|
||||
## Ce nu s-a putut stabili si de ce
|
||||
|
||||
- **Cine activeaza popup-urile `politica.mn2`/`contracte.mn2`** (nivelul chiar deasupra apelurilor
|
||||
directe la `factureaza(N)`) nu a fost trasat pana la butonul/grid-ul exact — nu era necesar pentru
|
||||
diff-ul procedurilor sau pentru tabelul de apelanti (punctul de interes e apelul la `factureaza`,
|
||||
nu inca un nivel de indirectare deasupra), dar daca implementarea muta si aceste meniuri, merita
|
||||
o trecere separata.
|
||||
- **Daca alte produse (ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB, ROACONT) au propriul lor
|
||||
`oproceduri_facturare.prg`/`ocomenzi.vc2` invocat din cod specific produsului, dincolo de ce am
|
||||
cautat** — cautarea a acoperit exact numele `factureaza`/`factureaza2` si cei noua wrapperi din
|
||||
`oproceduri_facturare.prg`; nu a acoperit eventuale cai complet diferite catre acelasi fisier
|
||||
(de exemplu un meniu `.mn2` specific unui alt produs care ar chema un wrapper cu alt nume). Pentru
|
||||
aceste cinci produse, cautarea directa (`factureaza(`, cei noua wrapperi) a dat zero rezultate in
|
||||
codul specific produsului — suficient pentru concluzia "nu apeleaza azi", dar nu e o dovada
|
||||
negativa absoluta de "nu ar putea apela niciodata prin alt canal".
|
||||
- **Testarea reala pe ROACONTRACTE** nu a fost efectuata (read-only, fara rulare de cod) — doar
|
||||
confirmata existenta apelului in sursa (`ferestre_contracte.vc2:1542-1546`).
|
||||
- **Diferenta minora de text** intre mesajele "Luna inchisa" ale celor doua proceduri
|
||||
(`factureaza:105` vs `factureaza2:594`, un caracter diacritic afisat diferit la citire) nu a fost
|
||||
investigata mai departe — pare artefact de encoding la citire (cp1250 in fisier), nu o diferenta
|
||||
de continut intentionata; oricum dispare odata cu stergerea lui `factureaza2`.
|
||||
|
||||
## Bug-uri semnalate, nereparate
|
||||
|
||||
- **`factureaza2` e, in forma actuala, non-functional dincolo de deschiderea formularului de
|
||||
antet dezactivata** — chiar daca cineva ar apela azi acest cod pe o cale ipotetica ocolind
|
||||
`factureaza`, executia interogarii SQL e dezactivata (`lnSucces = 1` hardcodat la `:827`, fara
|
||||
`goExecutor.oExecute`) si verificarea "nu exista articole" e dezactivata (`:838`) — codul ar merge
|
||||
mereu pe ramura de succes cu un cursor `crsarticole` nepopulat de aceasta rulare (posibil ramas
|
||||
dintr-un apel anterior, cu alt `tnTip`). Nu se repara aici — S2 il sterge oricum, deci bug-ul
|
||||
dispare odata cu procedura, nu prin fix separat.
|
||||
- Niciun bug nou gasit in `COMUN\clase\ofacturare_comun.vc2` sau `COMUN\programe\ofacturare_editare.prg`
|
||||
(perimetrul #6) — singura atingere a acestor fisiere in aceasta cercetare a fost citirea liniei
|
||||
`copiere_factura` din `ofacturare_comun.vc2:3710`, care nu are nimic suspect.
|
||||
478
docs/cercetare/s3_portare_antet.md
Normal file
478
docs/cercetare/s3_portare_antet.md
Normal file
@@ -0,0 +1,478 @@
|
||||
# Cercetare — proiectare S3: portarea logicii de antet in formularul unificat
|
||||
|
||||
Investigatie READ-ONLY pentru povestea **S3** din `docs\plan_13_unificare_formular_facturare.md:1428-1439`.
|
||||
Fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit. Constrangerile din briefing —
|
||||
`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` doar citite (perimetrul
|
||||
#6), `pack_facturare` si `pack_facturare_comun`-ul intern neatinse, analiticele read-only — respectate.
|
||||
|
||||
## Verdict (5-10 randuri)
|
||||
|
||||
S3 e **mai mare decat inventarul din plan sugereaza pe numar de metode** (29 `do_cauta_*` reale, nu
|
||||
30 — vezi punctul 1), dar **mult mai mare decat sugereaza pe complexitate reala**, pentru doua motive
|
||||
descoperite aici, nu presupuse: (a) `Init`, `inainte_de_do_termin` si — partial — `do_cauta_fdoc` sunt
|
||||
**omonime cu semantica total diferita pe toate cele patru formulare-sursa** (antet vs. articole vs.
|
||||
alte-date), deci portarea "o singura data" cere de fapt patru fuziuni de metoda, nu una singura cu
|
||||
ramificare; (b) bug-ul **#16 e real, reprodus pe cod pana la linia exacta**, si **nu e un bug de focus**
|
||||
— e o consecinta a faptului ca bucla de reincercare din `factureaza`/`factureaza2`
|
||||
(`ofacturare.prg:174-571`) trateaza **orice esec SQL** (nu doar cursul lipsa) ca pe un "DA, continui cu
|
||||
alt document", redeschizand din senin formularul de antet de la zero. Unificarea **nu-l reproduce
|
||||
automat** — poate chiar sa-l elimine ca efect secundar, daca antetul nu se mai reconstruieste de la
|
||||
Init dupa un esec Oracle in pasul urmator. Obstacolul principal pentru implementare nu e portarea
|
||||
codului de validare (majoritatea e `Do Case`/`amessagebox` mecanic), ci **reconcilierea a patru cicluri
|
||||
de viata `Init`/`inainte_de_do_termin` independente intr-unul singur**, cu ordinea de dependente de la
|
||||
punctul 5.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventarul metodelor de portat — numere corectate
|
||||
|
||||
Sursa: `vfp_symbols.ps1 -Class frm_date_factura` / `-Class frm_date_aviz`, confirmat pe fisierul real
|
||||
`COMUN\clase\ofacturare.vc2` (nu `.bak`).
|
||||
|
||||
**`frm_date_factura`** (`ofacturare.vc2:8482-9869`) — **16** metode `do_cauta_*`, nu 17:
|
||||
|
||||
| Metoda | Linii | Ce face | Echivalent in `frm_facturare_articole2` |
|
||||
|---|---|---|---|
|
||||
| `do_cauta_altele` | `8940-8961` (21) | cautare pe campul cu eticheta dinamica ("Altele"/contract/comanda/etc.) | nu |
|
||||
| `do_cauta_avize` | `8963-8999` (36) | cautare aviz-sursa (tip=4) | nu |
|
||||
| `do_cauta_client` | `9001-9060` (59) | cautare client, populeaza sold/cod fiscal | nu |
|
||||
| `do_cauta_comanda` | `9062-9090` (28) | cautare comanda-sursa | nu |
|
||||
| `do_cauta_contract` | `9092-9153` (61) | cautare contract-sursa | nu |
|
||||
| `do_cauta_factura` | `9155-9171` (16) | cautare factura-sursa (credit note, tip 7) | nu |
|
||||
| `do_cauta_facturi` | `9173-9212` (39) | cautare multi-factura (retur, tip 8/9) | nu |
|
||||
| `do_cauta_fdoc` | `9214-9225` (11) | cautare generica fel-document, `caut_fdoc()` — **identica byte-cu-byte cu varianta din `frm_date_aviz`**, vezi punctul 4 | nu |
|
||||
| `do_cauta_gestiune_init` | `9227-9242` (15) | cautare gestiune sursa | nu |
|
||||
| `do_cauta_locatii` | `9244-9253` (9) | cautare locatie (tip 45) | nu |
|
||||
| `do_cauta_lucrare` | `9255-9279` (24) | cautare lucrare | nu |
|
||||
| `do_cauta_responsabil` | `9281-9301` (20) | cautare responsabil | nu |
|
||||
| `do_cauta_sectie` | `9303-9342` (39) | cautare sectie | nu |
|
||||
| `do_cauta_valuta` | `9344-9359` (15) | cautare valuta, muta focus pe `clb_zi_curs` daca exista, altfel pe serie (`:9351-9356`, deja conditionat `Type(...)<>'U'`) | nu |
|
||||
| `do_cauta_venchelt` | `9361-9391` (30) | cautare venit/cheltuiala | nu |
|
||||
| `do_cauta_venit` | `9393-9394` (1) | stub/alias, o linie — de verificat ce mai apeleaza | nu |
|
||||
| `do_schimba_tipdoc` | `9396-9438` (42) | vezi punctul 6 — schimba `poDate.nIdTipDoc` si reinitializeaza serie+numar | **nu** — apelat orfan din prototip, vezi punctul 3 |
|
||||
| `do_verifica` | `9440-9453` (13) | verificare ANAF pe cod fiscal client | nu (dar exista pe `frm_facturi`, aceeasi semantica) |
|
||||
| `inainte_de_do_termin` | `9455-9561` (106) | validare completa antet inainte de trecerea la articole | **omonim cu semantica diferita** pe toate 4 formulare, vezi mai jos |
|
||||
| `Init` | `9563-9796` (234) | vezi punctul 5 | **omonim cu semantica diferita**, vezi mai jos |
|
||||
| `Clb_dataact...LostFocus` | `9798-9813` (15) | sincronizeaza `zi_curs=dataact` daca `clb_zi_curs` exista | nu |
|
||||
| `Clb_dataireg...LostFocus` | `9815-9831` (16) | idem, pe `dataireg` | nu |
|
||||
| `Clb_nract...LostFocus` | `9833-9835` (2) | — | nu |
|
||||
| `Clb_serie_act...LostFocus` | `9837-9844` (7) | — | nu |
|
||||
| `Ct_clb_fdoc._combobox1.Init` | `9846-9855` (9) | populeaza combo-ul FACTURA/PROFORMA/BON FISCAL | nu |
|
||||
| `Ct_clb_fdoc._combobox1.LostFocus` | `9857-9859` (2) | `thisform.do_schimba_tipdoc()` — vezi punctul 6 | nu (frm_facturare_articole2 are un apel similar, dar **orfan**, vezi punctul 3) |
|
||||
| `Ed_tx_simplu1._EDBASE1.KeyPress` | `9861-9867` (6) | — | nu |
|
||||
|
||||
**`frm_date_aviz`** (`ofacturare.vc2:6566-7618`) — **13** metode `do_cauta_*`, numarul din plan e corect:
|
||||
|
||||
| Metoda | Linii | Observatie |
|
||||
|---|---|---|
|
||||
| `do_cauta_altele` | `6882-6894` (12) | mai scurta decat pe factura (21) — set de tipuri mai mic |
|
||||
| `do_cauta_avize` | `6896-6930` (34) | apropiata ca marime de factura (36) |
|
||||
| `do_cauta_client` | `6932-7009` (77) | **mai lunga** decat pe factura (59) — trateaza eticheta variabila "Retur de la"/"Gestiune sursa" pe transfer |
|
||||
| `do_cauta_comanda` | `7011-7034` (23) | |
|
||||
| `do_cauta_contract` | `7036-7091` (55) | |
|
||||
| `do_cauta_fdoc` | `7093-7104` (11) | **identica cu factura**, vezi punctul 4 |
|
||||
| `do_cauta_gestiune_dest` | `7106-7131` (25) | **doar pe aviz** — gestiune destinatie la transfer subunitati |
|
||||
| `do_cauta_gestiune_init` | `7133-7144` (11) | mai scurta decat pe factura (15) |
|
||||
| `do_cauta_lucrare` | `7146-7165` (19) | |
|
||||
| `do_cauta_politica` | `7167-7182` (15) | **doar pe aviz** — `Ct_clb_politici_preturi` |
|
||||
| `do_cauta_responsabil` | `7184-7204` (20) | aceeasi lungime ca factura |
|
||||
| `do_cauta_sectie` | `7206-7240` (34) | |
|
||||
| `do_cauta_venchelt` | `7242-7275` (33) | |
|
||||
| `inainte_de_do_termin` | `7277-7352` (75) | **omonim**, vezi mai jos — mai scurta decat factura (106): fara validare data-scadenta, fara validare valuta, fara validare client (!) |
|
||||
| `Init` | `7354-7600` (247) | **omonim**, nu citit integral aici (doar `9563-9650`+`9733-9796` echivalentul pe factura) |
|
||||
| `Clb_dataact...LostFocus` | `7602-7605` | |
|
||||
| `Clb_dataireg...LostFocus` | `7607-7612` | |
|
||||
| `Clb_nract...LostFocus` | `7614-7616` | |
|
||||
|
||||
**`frm_date_aviz` nu are `do_schimba_tipdoc`** — confirmat, container-ul `Ct_clb_fdoc` de pe aviz e
|
||||
o cautare simpla, fara combo si fara evenimentul `LostFocus` care declanseaza schimbarea de tip.
|
||||
|
||||
**Total real: 16 + 13 = 29 metode `do_cauta_*`**, plus `do_schimba_tipdoc` (doar factura), `do_verifica`
|
||||
(doar factura), `inainte_de_do_termin` si `Init` (omonime pe ambele) — planul le numara global corect
|
||||
ca "17+13 ... plus restul", dar cifra "17" pe factura e gresita cu o unitate: **16**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Metodele omonime — capcana centrala a portarii
|
||||
|
||||
**Nivelul care conteaza cel mai mult, necunoscut inca in plan**: `Init` si `inainte_de_do_termin` sunt
|
||||
**acelasi nume de metoda pe toate cele patru formulare-sursa implicate in unificare**
|
||||
(`frm_date_factura`, `frm_date_aviz`, `frm_facturare_articole`/`frm_facturare_articole2`,
|
||||
`frm_alte_date`), fiecare cu corp complet diferit (antet vs. compunere articole vs. date suplimentare).
|
||||
O singura clasa unificata nu poate avea patru metode `Init`. **Portarea "o singura data" a S3 inseamna
|
||||
concret: patru corpuri de `Init` si patru corpuri de `inainte_de_do_termin` trebuie topite intr-un
|
||||
singur `Init` si un singur `inainte_de_do_termin` (sau redenumite si inlantuite explicit)** — asta e
|
||||
efortul real, nu doar mutarea codului de validare camp-cu-camp.
|
||||
|
||||
**`inainte_de_do_termin`, factura vs. aviz — diferenta reala, cu citat** (comparate direct, ambele corpuri
|
||||
citite integral, `ofacturare.vc2:9455-9561` si `:7277-7352`):
|
||||
|
||||
- **Factura valideaza `id_client` obligatoriu; aviz nu valideaza deloc acest camp**:
|
||||
```
|
||||
ofacturare.vc2:9510-9513 (doar pe factura)
|
||||
Case Empty(poDate.id_client) Or Isnull(poDate.id_client)
|
||||
amessagebox("Nu ati ales clientul!",48,"Atentie")
|
||||
This.ct_clb_nume_client.SetFocus()
|
||||
```
|
||||
Motiv plauzibil (neconfirmat mai departe): avizele de transfer intre subunitati (tip 23/25/30/41) nu
|
||||
au neaparat un "client" — au o gestiune destinatie, validata separat.
|
||||
- **Factura valideaza scadenta si valuta; aviz nu are deloc aceste `Case`-uri** — `Empty(poDate.datascad)`,
|
||||
`poDate.datascad<poDate.dataact`, `poDate.in_valuta=1 And Empty(zi_curs)`, `poDate.in_valuta=1 And
|
||||
Empty(id_valuta)` (`:9471-9493`) — aviz nu are control de valuta pe formular (S1, randul "Valuta").
|
||||
- **Aviz valideaza politica de preturi; factura nu**:
|
||||
```
|
||||
ofacturare.vc2:7334-7337 (doar pe aviz)
|
||||
Case Empty(Nvl(poDate.id_pol,0)) And InList(poDate.tip,23,41)
|
||||
amessagebox("Nu ati ales politica de preturi!",48,"Atentie")
|
||||
```
|
||||
- **Setul de tipuri si mesaje pentru "sursa neselectata" (`listaid`) e complet diferit**: factura
|
||||
`!Inlist(poDate.tip,1,5,7,8,9,10,48,49)` cu mesaje contract/comanda/aviz/locatie (`:9528-9541`); aviz
|
||||
`Inlist(poDate.tip,21,23,25,26,28,41,42,47)` cu mesaje comanda/gestiune-destinatie/gestiune-retur/contract
|
||||
(`:7318-7333`) — seturile de `tip` nu se suprapun deloc (factura = tipuri <21 sau in {45,48,49,51,52};
|
||||
aviz = tipuri >=21 in afara de 27/30). **Discriminatorul natural pentru ramificare exista deja**:
|
||||
`poDate.nIdTipDoc` (5=FACTURA, 6=AVIZ — calculat de apelant la `ofacturare.prg:192-196`) sau simpla
|
||||
apartenenta a lui `poDate.tip` la unul din cele doua seturi disjuncte, **nu** un flag nou.
|
||||
- **Contul folosit la verificarea de facturi duplicate difera**: `facturi_duplicate('4111', 0, ...)`
|
||||
(factura, `:9545`, cont clienti) vs. `facturi_duplicate('418', 0, ...)` (aviz, `:7341`, alt cont) —
|
||||
un literal hardcodat care trebuie sa devina el insusi parte din ramificare, nu doar mesajul.
|
||||
|
||||
**Alte omonime cunoscute, cu semantica diferita, relevante pentru zona din jurul lui S3** (nu introduse
|
||||
aici — reconfirmate, vezi si `docs\handoff_13_formular_unificat.md:192-194`):
|
||||
- `do_calculeaza_discount` — **la nivel de document** pe `frm_facturare_articole` (`:13405-13424`) si
|
||||
`frm_facturare_articole2` (`:17670-17686`, verificat aici: identic ca semantica, scrie
|
||||
`Thisform.ndiscfactron`/`ndiscfactval`); **la nivel de linie** pe `frm_articol_factura` (`:1874-1976`,
|
||||
scrie `poArticol.discount_unitar*`). Nu intra direct in S3 (nu e printre metodele antetului), dar
|
||||
orice cod nou care apeleaza `do_calculeaza_discount` prin nume pe formularul unificat trebuie sa
|
||||
stie care semantica o vrea.
|
||||
- `do_verifica` — pe `frm_date_factura` (`:9440-9453`, verificare ANAF) si pe `frm_facturi`
|
||||
(verificare ANAF, aceeasi semantica) — omonim inofensiv, aceeasi actiune.
|
||||
- `do_cauta_gestiune_init` / `do_cauta_responsabil` / `do_cauta_sectie` / `do_cauta_venchelt` /
|
||||
`do_cauta_altele` / `do_cauta_comanda` / `do_cauta_contract` / `do_cauta_avize` / `do_cauta_lucrare` —
|
||||
**acelasi nume pe factura si aviz, lungimi diferite** (vezi tabelele de la punctul 1) — nu omonime
|
||||
periculoase (aceeasi intentie: cautare pe camp), dar **nu identice** — portarea trebuie sa ia corpul
|
||||
fiecareia separat, nu sa presupuna ca sunt duplicate de sters.
|
||||
- **Singura pereche confirmata identica byte-cu-byte**: `do_cauta_fdoc` (vezi punctul 4) — aceasta
|
||||
chiar poate fi portata o singura data, fara ramificare.
|
||||
|
||||
---
|
||||
|
||||
## 3. Ce e deja in prototip vs. ce lipseste
|
||||
|
||||
`frm_facturare_articole2` (`ofacturare.vc2:15741-19355`) are controalele de antet montate direct pe
|
||||
formular (confirmat in `inventar_controale_formulare.md`), dar **zero logica**:
|
||||
|
||||
| Metoda/mecanism | Exista in `frm_facturare_articole2`? | Detaliu |
|
||||
|---|---|---|
|
||||
| Toate cele 29 `do_cauta_*` | **NU** | niciunul in lista de metode proprii a clasei (confirmat cu `vfp_symbols.ps1 -Class`) |
|
||||
| `do_schimba_tipdoc` | **NU, dar e APELAT** — cod orfan | `clb_fdoc.cboFdoc.Valid` (`:19255-19257`) contine `thisform.do_schimba_tipdoc()`, dar clasa **nu are** aceasta metoda in lista proprie. Daca userul ar schimba azi combo-ul `clb_fdoc` pe acest formular mort, ar cadea cu eroare de metoda inexistenta. Confirma independent constatarea din plan ("controalele sunt acolo, logica nu") — de fapt e mai rau: e cod care ar crapa daca s-ar activa calea, nu doar cod lipsa |
|
||||
| `inainte_de_do_termin` | **DA, dar cu alta semantica** | `:18809-18986` (178 linii) — valideaza ARTICOLE (stoc, discount, TVA), nu antetul; e omonimul de care vorbeste punctul 2, nu un candidat de reutilizare pentru validarea de antet |
|
||||
| `Init` | **DA, dar cu alta semantica** | `:18988-19080` (92 linii, citit integral aici) — confirmat: doar grid/curs-label/total-mode/coloane in/afara valuta; **niciun** `poDate.xxx -> control.Value`. Nu populeaza antetul deloc |
|
||||
| Serie/numar (`Clb_serie_act1`/`Clb_nract`) | Controale prezente, dar fara wiring de populare in `Init` | `Clb_serie_act1.TEXT_SIMPLU1.LostFocus` (`:19263-19270`) exista ca metoda proprie — nu verificat aici daca reproduce `genereazanumar` |
|
||||
|
||||
**Concluzie punctul 3**: prototipul e o **coaja vizuala**, nu un schelet de 50% functional. Ce trebuie
|
||||
scris pentru S3 e efectiv tot codul de comportament — controalele economisesc timpul de aranjare in
|
||||
`.scx`/`.vcx`, nu timpul de portare a logicii.
|
||||
|
||||
---
|
||||
|
||||
## 4. Ramificarea factura / aviz in interior
|
||||
|
||||
**Discriminatorul de ramificare recomandat: `poDate.nIdTipDoc`** (5=FACTURA, 6=AVIZ), deja calculat de
|
||||
apelant inainte de `Createobject` (`ofacturare.prg:187-196`) si deja disponibil pe `poDate` in
|
||||
momentul in care formularul unificat ar porni `Init`. Alternativ, acelasi rezultat se obtine testand
|
||||
direct apartenenta lui `poDate.tip` la unul din cele doua seturi disjuncte folosite azi in
|
||||
`inainte_de_do_termin` (vezi punctul 2) — cele doua seturi nu se suprapun niciodata, deci nu exista
|
||||
ambiguitate. **Nu e nevoie de o proprietate noua** gen `lEsteAviz` — `nIdTipDoc` face deja treaba, si
|
||||
e deja pe obiectul `poDate` care trece prin tot lantul de apeluri.
|
||||
|
||||
Ce trebuie sa ramifice concret, cu dovada:
|
||||
1. **`inainte_de_do_termin`** — Cases specifice fiecarei parti (vezi citatele de la punctul 2), plus
|
||||
constanta de cont (`'4111'` vs `'418'`) la `facturi_duplicate`.
|
||||
2. **`Init`** — seturile `Do Case poDate.tip` care decid ce se elimina (`RemoveObject`) difera pe
|
||||
fiecare parte (S1 documenteaza deja fiecare camp cu conditia lui de vizibilitate; portarea trebuie
|
||||
sa pastreze fiecare conditie identic, nu sa le generalizeze pe ghicite).
|
||||
3. **`do_schimba_tipdoc`** — **exista doar pe factura**; pe aviz nu exista mecanismul de schimbare a
|
||||
tipului de document dupa deschiderea formularului (containerul `ct_clb_fdoc` de pe aviz e cautare
|
||||
simpla, fara `_combobox1`). Ramificarea aici nu e "cod diferit pe aceeasi metoda" ci "metoda
|
||||
prezenta doar pe o ramura" — formularul unificat trebuie sa decida daca aviz capata acest
|
||||
comportament nou (schimbare tip dupa deschidere) sau ramane fara el, ca azi.
|
||||
4. **`do_cauta_fdoc`** — **nu ramifica**, e identic (punctul 1) — poate fi portat o singura data, fara
|
||||
`If nIdTipDoc=...`.
|
||||
5. **`do_cauta_client`** — aviz e cu 18 linii mai lung (77 vs 59) pentru eticheta variabila
|
||||
"Retur de la"/"Gestiune sursa" pe transfer (S1, randul "Client") — de citit integral la
|
||||
implementare pentru ramificarea exacta, nu verificat linie-cu-linie aici.
|
||||
|
||||
---
|
||||
|
||||
## 5. `Init`-urile — ce face fiecare, in ce ordine, ce depinde de ce
|
||||
|
||||
**Secventa de azi, pe drumul normal (factura din lista de preturi, fara eroare Oracle):**
|
||||
|
||||
1. **Apelantul** (`ofacturare.prg:factureaza`, inainte de orice `Createobject`): construieste
|
||||
`poDate = Createobject("oDateFactura", ...)` si `poGeneratorNumere = Createobject("oGeneratorNumere")`
|
||||
(`:184-185`), seteaza `poDate.nIdTipDoc` (`:187-196`), cheama
|
||||
`poDate.completeaza_setari_document(...)` daca e copiere (`:200-206`), apoi
|
||||
`poGeneratorNumere.ResetNumere()` si `poDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)`
|
||||
(`:208-211`) — **toate acestea ruleaza inainte ca vreun formular sa existe**. Orice `Init` de mai jos
|
||||
presupune `poDate`/`poGeneratorNumere` deja populate.
|
||||
2. **`frm_date_factura.Init`** (`:9563-9796`, 234 linii, citit integral aici) — `DoDefault()`, apoi
|
||||
masoara inaltimile containerelor de antet intr-un array (`laPozitii`), decide pe `Do Case
|
||||
gnScadereStoc/poDate.tip` ce containere elimina (`RemoveObject`) si reface layout-ul pe verticala,
|
||||
initializeaza serie+numar (`.clb_serie_act.do_initializeaza(poDate.rezultat_serii)`, `:9737`), si
|
||||
**la final** seteaza focus: pe `ct_clb_valuta` daca documentul e in valuta si campul valutei apare
|
||||
deasupra tipului si nu are inca valuta aleasa, **altfel pe `ct_clb_fdoc`** (`:9788-9793`, citat
|
||||
integral la punctul 6). Depinde de: `poDate` (tip, in_valuta, ...), `poGeneratorNumere`
|
||||
(`creeaza_cursor_serii` deja rulat de apelant), variabile globale de firma (`gnScadereStoc`).
|
||||
3. **`frm_facturare_articole2.Init`** (`:18988-19080`, 92 linii, citit integral aici) — confirmat:
|
||||
**nu atinge niciun camp de antet**. Configureaza doar gridul (`nNrInregVizibile`), eticheta
|
||||
cursurilor valutare, modul total (`gnModTotFact`), si elimina coloanele RON sau valuta din grid
|
||||
dupa `poDate.in_valuta`. Depinde de: `poDate.zi_curs`/`in_valuta`/`tip`, `crscursuri` (populat de
|
||||
apelant intre pasii 2 si 3, nu de acest `Init`).
|
||||
4. **`frm_alte_date.Init`** (`ferestre_cere_date.vc2:3105-3207`, 103 linii, citit integral aici) —
|
||||
`DoDefault()`, citeste `poDate.dataora_exp`; **daca nu e proforma**: cauta ultimul delegat/masina
|
||||
folosite pentru client (apel Oracle `cauta_date_ultima_factura[_tip]`, `:3119-3136`), populeaza
|
||||
combo-ul de casa dintr-un cursor Oracle nou (`v_nom_casa`, `:3156-3173`), seteaza `opt_incasat`
|
||||
implicit daca `poDate.incasat<>0`; **daca e proforma**: elimina complet grupul delegat/masina/
|
||||
incasare (`RemoveObject` in cascada, `:3192-3201`) si redimensioneaza formularul la zona de text
|
||||
aditional. **Risc gasit, neurmarit mai departe**: pe eroare la interogarea combo-ului de casa,
|
||||
`Init` cheama `poGeneratorNumere.dezaloca_numar(5)` si `Return` (`:3159-3161`) — acelasi tipar de
|
||||
"dezalocare numar pe eroare SQL neasteptata" ca la bug-ul #16 (punctul 6), dar intr-un `Init`, nu
|
||||
intr-o bucla — **nu s-a verificat daca are aceeasi consecinta de recreare completa a formularului**;
|
||||
semnalat, nu investigat suplimentar aici din motive de buget de context.
|
||||
|
||||
**Ordinea de dependente, explicit**: `poDate`+`poGeneratorNumere` (apelant) -> `frm_date_factura`/
|
||||
`frm_date_aviz.Init` (foloseste serie/numar deja create) -> **Oracle: cursorul de articole**
|
||||
(`ofacturare.prg:266-308`, aici poate pica bug #16) -> `frm_facturare_articole2.Init` (foloseste
|
||||
`crscursuri` populat intre pasi, nu de propriul `Init`) -> `frm_alte_date.Init` (face **inca** un apel
|
||||
Oracle nou, cu propriul risc de dezalocare). **Pentru formularul unificat, aceasta secventa de patru
|
||||
`Init`-uri trebuie sa devina un singur `Init` care ruleaza fazat** (o singura data la deschidere) —
|
||||
sau ramane o discutie deschisa daca vreo faza (Oracle-lookup-ul din `frm_alte_date.Init`) ramane
|
||||
intarziata pana la deschiderea sectiunii pliate, ca sa nu incarce round-trip-uri Oracle inutile cand
|
||||
utilizatorul nu ajunge niciodata la acea sectiune.
|
||||
|
||||
---
|
||||
|
||||
## 6. Bug-ul #16 — mecanismul exact, verificat pe cod
|
||||
|
||||
**Text original** (`COMUN\docs\todos.txt:45`): *"la revenire din formularul de curs valutar, focusul
|
||||
revine inainte de numar document, cred ca pe TIP DOCUMENT, si la iesire din serie se regenereaza numar
|
||||
act, ceea ce este periculos daca utilizatorul l-a schimbat".*
|
||||
|
||||
**Verdict: NU e un bug de focus. E o bucla de reincercare care trateaza orice esec Oracle ca pe un "DA,
|
||||
mai fac un document" si reconstruieste formularul de antet de la zero — focusul si regenerarea
|
||||
numarului sunt doar simptomele vizibile ale acestei reconstructii.**
|
||||
|
||||
Lantul complet, verificat linie cu linie:
|
||||
|
||||
1. Utilizatorul completeaza antetul, apasa Termina — `inainte_de_do_termin` trece, formularul de antet
|
||||
se inchide (`pnButon=1`).
|
||||
2. Apelantul (`factureaza`, `ofacturare.prg:266-308`) construieste cursorul de articole din Oracle
|
||||
(`cursor_preturi`/etc., functie de `poDate.tip`). **Daca cererea esueaza** (`lnSucces<0`,
|
||||
`:313`) — inclusiv, dar nu numai, cu eroarea Oracle `-20005` "Nu este setat cursul..." (curs
|
||||
valutar lipsa pentru o valuta din listele de preturi ale utilizatorului, verificata neconditionat
|
||||
in `pack_facturare.verifica_cursuri_valute`, documentat deja in `zi_curs_validare.md`):
|
||||
```
|
||||
ofacturare.prg:313-322
|
||||
If lnSucces < 0
|
||||
AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
|
||||
If goExecutor.nEroare = 20005
|
||||
vizualizeaza_curs(poDate.zi_curs) && deschide "formularul de curs valutar" (frm_curs, modal)
|
||||
ENDIF
|
||||
poGeneratorNumere.dezaloca_numar(poDate.nIdTipDoc) && numarul alocat se elibereaza
|
||||
Else
|
||||
... [singurul loc unde se cere "Doriti sa continuati?" si se seteaza lnRaspuns]
|
||||
Endif
|
||||
```
|
||||
`vizualizeaza_curs` (`oproceduri_curs.prg:8-42`) e confirmat: deschide `Createobject("frm_curs",
|
||||
tdDataCurs).Show(1)` — chiar formularul din reclamatie.
|
||||
3. **Punctul central, verificat prin numararea `If`/`Else`/`Endif` din fisier**: prompt-ul "Doriti sa
|
||||
continuati cu operatii de acest fel?" care seteaza variabila de bucla `lnRaspuns` **exista doar in
|
||||
ramura `Else` a lui `If lnSucces<0`** (`ofacturare.prg:555-564`, in interiorul aceluiasi bloc
|
||||
`If/Else/Endif` care se inchide abia la `:568`). **Pe ramura de eroare (`lnSucces<0`), `lnRaspuns`
|
||||
nu e niciodata atins.** Isi pastreaza valoarea din intrarea in aceasta iteratie a buclei — pe primul
|
||||
document, `6` (initializat la `:174`, inainte de `Do While lnRaspuns = 6` de la `:175`).
|
||||
4. Bucla externa (`Do While lnRaspuns = 6 ... Enddo`, `:175-571`) **reintra automat**, fara nicio
|
||||
intrebare catre utilizator, pentru ca `lnRaspuns` e inca `6`. In aceasta noua iteratie:
|
||||
`poGeneratorNumere.ResetNumere()` si `poDate.rezultat_serii = poGeneratorNumere.creeaza_cursor_serii(...)`
|
||||
ruleaza din nou (`:208-211`), apoi se creeaza **o instanta noua** de `frm_date_factura`/`frm_date_aviz`
|
||||
(`Createobject`, `:230`) si se arata modal (`:235`) — `poDate` insusi **nu** e recreat (ramane
|
||||
acelasi obiect, cu `nract`/`serie_act` deja setate de utilizator), dar **formularul da**, deci
|
||||
`Init` ruleaza de la capat.
|
||||
5. `frm_date_factura.Init` (`:9563-9796`) reface layout-ul si, **la final, seteaza focus necondiționat
|
||||
pe `ct_clb_fdoc`** (tip document) in cazul normal:
|
||||
```
|
||||
ofacturare.vc2:9788-9793
|
||||
DO CASE
|
||||
CASE poDate.in_valuta = 1 and this.ct_clb_valuta.Top < this.ct_clb_fdoc.Top AND EMPTY(NVL(poDate.nume_valuta,''))
|
||||
this.ct_clb_valuta.SetFocus()
|
||||
OTHERWISE
|
||||
this.ct_clb_fdoc.SetFocus()
|
||||
ENDCASE
|
||||
```
|
||||
**Aceasta e "focusul care revine pe TIP DOCUMENT"** din reclamatie — nu un bug izolat de focus, ci
|
||||
comportamentul normal de `Init` al unui formular nou, aparut unde utilizatorul nu se astepta la un
|
||||
formular nou.
|
||||
6. Cand focusul paraseste `ct_clb_fdoc` (Tab, click in alta parte — orice), se declanseaza
|
||||
`Ct_clb_fdoc._combobox1.LostFocus -> thisform.do_schimba_tipdoc()` (`:9857-9859`). Prima linie a
|
||||
acestei metode, **inainte de orice verificare "tipul chiar s-a schimbat?"**:
|
||||
```
|
||||
ofacturare.vc2:9417 (in do_schimba_tipdoc, INAINTE de guard-ul de la :9419-9421)
|
||||
This.clb_serie_act._cbbase1.LostFocus()
|
||||
```
|
||||
— apeleaza **neconditionat** handler-ul de LostFocus al combo-ului de serie, indiferent daca tipul
|
||||
documentului s-a schimbat sau nu.
|
||||
7. Acel handler (`serii_numere.vc2:138-140`, clasa `clb_serie_act`) cheama `genereazanumar` (`:114-136`):
|
||||
```
|
||||
serii_numere.vc2:122-127
|
||||
If poGeneratorNumere.verifica_serie(This.nid_tipdoc) Or &lcValoare. = 0
|
||||
lcValoare = lcValoare+[=]+ALLTRIM(Str(poGeneratorNumere.aloca_numar(This.nid_tipdoc,Null),20,0))
|
||||
&lcValoare
|
||||
This.Parent.Refresh()
|
||||
Endif
|
||||
```
|
||||
— **aloca un numar nou** si il scrie peste `poDate.nract` prin macro, **necondiționat de ce numar
|
||||
avea utilizatorul inainte**. **Asta e "iesirea din serie regenereaza numarul actului"** din
|
||||
reclamatie — confirmat exact, pana la linia care face scrierea.
|
||||
|
||||
**Verdict pe intrebarea planului ("se rezolva #16 in S3, sau se reproduce bug-ul?"): se poate rezolva
|
||||
in S3, cu un cost mic, dar nu e o consecinta automata a unificarii — trebuie tratat explicit.**
|
||||
Cauza reala nu e in `frm_date_factura`/`do_schimba_tipdoc`/`clb_serie_act` (cod care se comporta
|
||||
"corect" fata de contractul lui local), ci in bucla de reincercare din apelant
|
||||
(`ofacturare.prg:174-571`), care **nu distinge "utilizatorul a confirmat ca vrea alt document" de
|
||||
"cererea Oracle a esuat"**. Doua directii posibile, ambele in afara perimetrului strict al portarii de
|
||||
antet, dar declansate direct de ea:
|
||||
- **(a) minimal**: in formularul unificat, dupa un esec Oracle la pasul de construire a cursorului de
|
||||
articole (echivalentul liniei `:313`), **nu se distruge formularul de antet** — antetul e deja o
|
||||
singura sectiune persistenta a aceluiasi formular, nu un obiect separat recreat de apelant; simpla
|
||||
arhitectura unificata (un `Init` per sesiune de emitere, nu per formular) elimina pasul 4 de mai sus,
|
||||
deci elimina intreg lantul 5-7. **Acesta e motivul pentru care unificarea are sansa reala sa rezolve
|
||||
#16 ca efect secundar** — dar numai daca implementarea nu recreeaza formularul intreg la reincercare
|
||||
dupa eroare Oracle (ceea ce ar reproduce bug-ul identic, doar mutat).
|
||||
- **(b) daca (a) nu e suficient** (de ex. daca dupa fix la curs tot trebuie relansata interogarea de
|
||||
articole): `lnRaspuns` (sau echivalentul lui in noua arhitectura) trebuie resetat explicit pe orice
|
||||
cale care iese din ramura de eroare, ca sa nu mai fie confundat cu "utilizatorul a spus DA".
|
||||
|
||||
**Coordonarea cu #6 (decizia 30 si constrangerea din briefing) — verificat direct pe diff-uri, nu doar
|
||||
pe proza handoff-ului**: `docs\diff_s4_valuta_dialog*.patch` (trei fisiere) ating exclusiv
|
||||
`COMUN\clase\omodificari.vc2`, `COMUN\programe\ofacturare_editare.prg` si un fisier de test —
|
||||
**niciunul nu atinge `ofacturare.vc2` (unde traiesc `frm_date_factura`, `do_schimba_tipdoc`,
|
||||
`clb_serie_act`) sau `ofacturare.prg` (unde traieste bucla `factureaza`)**. Confirmat si de continutul
|
||||
lui handoff intermediar (sters): intreaga lui cercetare e despre `frm_articol_factura`
|
||||
(dialogul de adaugare articol pe linie, alt fisier/clasa) si `frm_modific2024.cmdAdaugaArticol.Click`
|
||||
— un mecanism de valuta **pe linie de articol**, fara nicio legatura cu antetul sau cu bucla de
|
||||
emitere. **#6 nu a atins deloc lantul lui #16. Bug-ul e in intregime valabil si neschimbat.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Ordinea de lucru propusa pentru S3
|
||||
|
||||
Pasi care lasa suita functionala dupa fiecare (calea veche ramane in productie pe tot parcursul —
|
||||
niciun pas nu sterge `frm_date_factura`/`frm_date_aviz`/`frm_alte_date` originale):
|
||||
|
||||
1. **Fuzioneaza `Init`-urile de antet** (`frm_date_factura` + `frm_date_aviz`) intr-o metoda unica pe
|
||||
formularul unificat, ramificata pe `poDate.nIdTipDoc` (punctul 4). Verificare: formularul unificat
|
||||
se deschide pe fiecare din cele ~15 tipuri principale de `poDate.tip` folosite azi in cele doua
|
||||
`Init`-uri, si arata exact aceleasi campuri vizibile/eliminate ca varianta veche — comparatie
|
||||
camp-cu-camp fata de tabelul din `docs\S1_inventar_campuri_formular_unificat.md`.
|
||||
2. **Porteaza cele 29 `do_cauta_*`** (fara ramificare unde sunt identice — `do_cauta_fdoc` — cu
|
||||
ramificare pe `nIdTipDoc` unde difera). Verificare: fiecare cautare deschide acelasi dialog, scrie
|
||||
aceleasi proprietati pe `poDate`, muta focusul identic cu azi.
|
||||
3. **Fuzioneaza `inainte_de_do_termin`**, cu ramificarea documentata la punctul 2. Verificare: aceleasi
|
||||
mesaje de eroare, in acelasi ordine, pentru aceleasi campuri goale, pe fiecare tip.
|
||||
4. **Porteaza `do_schimba_tipdoc`** — decide explicit daca ramane doar-factura sau se extinde si pe
|
||||
aviz (punctul 4, pct. 3). **Aici se rezolva sau nu #16** (punctul 6) — de facut ca parte a acestui
|
||||
pas, nu separat, pentru ca schimbarea de arhitectura care il rezolva (un singur `Init` persistent)
|
||||
e chiar cea care se construieste la pasul 1.
|
||||
5. **Porteaza `but_modifica`/blocarea antetului** (decizia 9, deja proiectata in plan sectiunea I) —
|
||||
depinde de pasii 1-4 fiind stabili, pentru ca reutilizeaza aceleasi controale.
|
||||
6. **Integreaza bucla de emitere** (`factureaza`/`factureaza2`) cu noul formular unificat, tratand
|
||||
explicit esecul Oracle de la cursorul de articole (punctul 6, directia a/b).
|
||||
|
||||
**Criteriul de "gata", rescris verificabil.** Azi planul spune "un document se emite integral din
|
||||
formularul unificat, pe tip 1, cu acelasi rezultat in `vanzari`/`act`/`rul` ca pe calea veche" — fara
|
||||
sa spuna cum se compara. Propunere concreta:
|
||||
- Se emite **acelasi document** (acelasi client, articole, cantitati, preturi) o data pe calea veche
|
||||
(`frm_date_factura` -> `frm_facturare_articole` -> `frm_alte_date`) si o data pe formularul unificat,
|
||||
pe date de test identice, in aceeasi zi contabila.
|
||||
- Se compara, randuri-cu-randuri, rezultatul in `VANZARI` (toate coloanele, nu doar sumele), `ACT`,
|
||||
`RUL`, `DOCUMENTE`, `JV2007` (nota contabila) — cel mai simplu cu doua interogari identice filtrate
|
||||
pe cei doi `ID_FACT`/`ID_VANZARE` rezultati, exportate si diff-uite text-cu-text (unealta: acelasi
|
||||
export SQL folosit deja pentru `PACK_FACTURARE` in `docs\ff_*.sql`, adaptat pe `SELECT * FROM VANZARI
|
||||
WHERE ID_FACT=...`).
|
||||
- Diferentele **asteptate** (marcate ca OK, nu ca esec): `ID_VANZARE`/`ID_FACT`/timestamp-uri de
|
||||
creare — orice altceva trebuie sa fie identic.
|
||||
- Se repeta pentru **cel putin un tip de factura in valuta** (S3 atinge direct campurile de valuta) si
|
||||
**un tip de aviz** (ramificarea de la punctul 4), nu doar tip 1.
|
||||
|
||||
---
|
||||
|
||||
## 8. Ce nu se poate testa headless din S3
|
||||
|
||||
Capcana cunoscuta (`COMUN\docs\depanare_testare_vfp.md`, memorie de proiect): sub harness `-A -T`,
|
||||
coloanele de grid nu se materializeaza (`ColumnCount=0`, `RecordSource` raman artefacte necitite) —
|
||||
relevant direct pentru `frm_facturare_articole2` (grid-ul de compunere), dar S3 propriu-zis nu atinge
|
||||
gridul.
|
||||
|
||||
Specific pentru S3 (antet):
|
||||
- **Toate cele 29 `do_cauta_*`** deschid un dialog modal de cautare (`caut_ora.vcx`/similare) —
|
||||
interactiunea reala de selectare dintr-o lista si `Show(1)` modal nu se poate simula headless; se
|
||||
pot testa doar efectele **dupa** ce `poCauta`/rezultatul e construit manual (tiparul deja folosit in
|
||||
`test_pret_cu_tva_dialog.prg`/`test_adauga_linie_valuta.prg` mentionat in
|
||||
handoff intermediar (sters), punctul 7) — apel direct al metodei cu un obiect
|
||||
simulat, fara `Show()`.
|
||||
- **`Init`-ul complet** (redimensionare, `RemoveObject` in cascada, repozitionare containere) e
|
||||
verificabil pe proprietati (`.Visible`, `.Height`, existenta obiectului dupa `Type(...)`) fara UI
|
||||
vizibil, pentru ca `Createobject` ruleaza `Init` fara `Show()` — tiparul e deja validat in codebase.
|
||||
- **Focusul si secventa reala de `LostFocus`** (exact ce a produs bug #16) **nu se poate reproduce
|
||||
headless** — necesita fie `vfp_ui_harness.ps1` cu UI vizibil si input simulat pe masina, fie testare
|
||||
manuala de Marius; simularea unui `SetFocus()`/`LostFocus()` apelat direct din cod ocoleste tocmai
|
||||
secventa evenimentelor native care a cauzat bugul.
|
||||
- **Eroarea Oracle -20005 si redeschiderea `vizualizeaza_curs`** — reproductibila headless doar daca
|
||||
se poate forta controlat lipsa unui curs pentru o data de test (manipulare de date, nu de UI) — nu
|
||||
s-a verificat aici daca exista deja o retetare de date de test pentru asta.
|
||||
|
||||
---
|
||||
|
||||
## Ce nu s-a putut stabili si de ce
|
||||
|
||||
- **Corpul complet al `frm_date_aviz.Init`** (`:7354-7600`, 247 linii) — citit doar partial in sesiuni
|
||||
anterioare (pana la `~7533`, conform `inventar_controale_formulare.md:166-167`); nu re-citit integral
|
||||
aici din motive de buget de context. Structura generala (Do Case pe tip, RemoveObject, focus final)
|
||||
e foarte probabil simetrica cu `frm_date_factura.Init`, dar nu verificata linie-cu-linie.
|
||||
- **`do_cauta_venit`** (`frm_date_factura`, `:9393-9394`, o singura linie) — nu s-a citit continutul;
|
||||
posibil alias/stub mort, de verificat la implementare.
|
||||
- **Riscul semnalat la punctul 5** (`frm_alte_date.Init:3159`, `dezaloca_numar` pe eroare la
|
||||
interogarea combo-ului de casa) — **doar semnalat, neurmarit**: nu s-a verificat daca apelantul care
|
||||
cheama `frm_alte_date` are aceeasi bucla "retry silentios" ca `factureaza`, sau daca eroarea aici
|
||||
chiar opreste fluxul curat (`Return` explicit la `:3161`, spre deosebire de bug #16 unde nu exista
|
||||
`Return` echivalent). Merita o cercetare separata, de marimea celei de la punctul 6, inainte de
|
||||
implementare.
|
||||
- **`Clb_serie_act1.TEXT_SIMPLU1.LostFocus`** din `frm_facturare_articole2` (`:19263-19270`) — nu
|
||||
citit; nu s-a verificat daca reproduce corect `genereazanumar` sau e alt cod orfan ca cel de la
|
||||
punctul 3.
|
||||
- **Ramificarea exacta linie-cu-linie pentru restul celor 27 de perechi `do_cauta_*`** (dincolo de
|
||||
`do_cauta_client` si `do_cauta_fdoc`, comparate direct aici) — s-au comparat doar lungimile (indiciu
|
||||
de diferenta), nu continutul; de citit la implementare, nu presupus din lungime.
|
||||
- **`frm_modifica_factura`** — clasa reala traieste in `ofacturare_comun.vc2` (perimetrul #6, doar
|
||||
citire permisa), dar `vfp_symbols.ps1` a indexat-o din `.pre_s4butoane.bak.vc2` (linii duplicate,
|
||||
nesigure) — nu s-au recitit liniile reale, pentru ca `frm_modifica_factura` **nu intra in portarea
|
||||
S3** (e inlocuita de `but_modifica`, deja proiectat separat in plan, sectiunea I).
|
||||
|
||||
## Bug-uri semnalate, nereparate
|
||||
|
||||
1. **Cod orfan in `frm_facturare_articole2`**: `clb_fdoc.cboFdoc.Valid` (`ofacturare.vc2:19256`) cheama
|
||||
`thisform.do_schimba_tipdoc()`, metoda **inexistenta** pe aceasta clasa — ar arunca eroare VFP daca
|
||||
userul ar interactiona cu acest combo pe formularul mort. Fara consecinte azi (formularul nu e
|
||||
accesibil in productie, cf. plan sectiunea A), dar de curatat sau completat cand prototipul devine
|
||||
baza formularului unificat.
|
||||
2. **Bug #16, confirmat si localizat complet** (punctul 6) — in `ofacturare.prg:555-564` (si simetric
|
||||
in `factureaza2`, liniile ~1061 dupa numerotarea echivalenta, nu verificate linie-cu-linie aici):
|
||||
`lnRaspuns` nu se reseteaza pe ramura de eroare Oracle a buclei de emitere, cauzand recrearea
|
||||
completa si nesolicitata a formularului de antet. In afara perimetrului `ofacturare_comun.vc2`/
|
||||
`ofacturare_editare.prg` (deci nu ciocneste cu #6), dar in `ofacturare.vc2`/`ofacturare.prg`, cod
|
||||
comun suitei (afecteaza si ROACONT/ROAGEST/etc. daca folosesc acelasi `factureaza`/`factureaza2`
|
||||
din `COMUN` — neverificat aici daca alte produse il apeleaza, dar fisierul e in `COMUN\programe`,
|
||||
deci probabil da).
|
||||
3. **Risc structural similar, neconfirmat**: `frm_alte_date.Init:3159` — acelasi tipar
|
||||
"`dezaloca_numar` pe eroare SQL neasteptata", posibil fara aceeasi consecinta de reincercare
|
||||
silentioasa, dar nu verificat (vezi "Ce nu s-a putut stabili").
|
||||
602
docs/cercetare/s3b_alte_date_analitice.md
Normal file
602
docs/cercetare/s3b_alte_date_analitice.md
Normal file
@@ -0,0 +1,602 @@
|
||||
# S3b — Proiectare: sectiunea pliata "alte date + analitice"
|
||||
|
||||
Livrabilul povestii **S3b** din `docs\plan_13_unificare_formular_facturare.md:1838-1854` (deciziile 7,
|
||||
8, 25, 26). Proiectare pe cod, fara nicio modificare — zero editari, zero
|
||||
`git_sync.ps1`/`txt2vcx.ps1`, zero commit. `COMUN\clase\ofacturare_comun.vc2` si
|
||||
`COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar citite, neatinse.
|
||||
|
||||
## Verdict (rezumat)
|
||||
|
||||
S3b nu e o simpla "mutare de controale" cum sugereaza textul din plan. **Descoperirea centrala,
|
||||
negasita in materialele existente**: mecanismul de alocare a numarului de chitanta nu porneste doar
|
||||
din clicul utilizatorului pe `opt_incasat` — porneste din **orice** atribuire programatica a
|
||||
proprietatii `.Value`, pentru ca `opt_incasat.ProgrammaticChange` (`ferestre_cere_date.vc2:3351-3352`)
|
||||
cheama exact acelasi `actualizeaza_tipincasare()` ca si `.Click` (`:3250-3251`). `Init`-ul de azi
|
||||
(`:3150`) seteaza chiar el `Thisform.opt_incasat.Value = 2` cand documentul soseste cu
|
||||
`poDate.incasat<>0` (caz real: copiere, confirmat la `ofacturare_stoc.prg:535`,
|
||||
`oproceduri_facturare.prg:1186`, `ofacturare_comun.vc2:4066,4260`) — deci **azi, deschiderea
|
||||
dialogului aloca deja un numar de chitanta fara ca userul sa atinga nimic**, de fiecare data cand
|
||||
documentul soseste cu o incasare presetata. In formularul unificat, unde zona nu se mai deschide o
|
||||
singura data modal ci se pliaza/depliaza repetat pe acelasi obiect `poDate` persistent, acelasi cod
|
||||
rulat la fiecare depliere ar realoca/dealoca la fiecare toggle — exact opusul criteriului cerut de
|
||||
decizia 25. Sectiunea 4 trateaza asta ca risc central, cu recomandare concreta.
|
||||
|
||||
A doua descoperire: **niciun mecanism de pliere reutilizabil nu exista** in prototipul
|
||||
`frm_facturare_articole2` (cautare completa, zero potriviri pe "plia/colaps/accordion/expand" in
|
||||
`ofacturare.vc2`) — cel mai apropiat tipar din suita e `frm_modific2024.afiseaza_rulaje`
|
||||
(`omodificari.vc2:13169-13199`, perimetrul #6, citit dar neatins), care refoloseste **acelasi idiom
|
||||
sus/jos** deja validat pentru `but_modifica`/`but_salveaza` (decizia 9), dar aplicat unui show/hide,
|
||||
nu unei blocari. Sectiunea 6 detaliaza.
|
||||
|
||||
A treia: pentru analiticele read-only (decizia 26), mecanismul existent `ct_clb_cautare.do_dezactiveaza()`
|
||||
(`caut_ora.vc2:800-806`) **ascunde doar lupa de cautare**, nu blocheaza si textbox-ul de afisare —
|
||||
cineva tot poate scrie liber in camp. Sectiunea 2 detaliaza gap-ul si completarea minima necesara.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventarul exact al celor patru grupuri din `frm_alte_date`
|
||||
|
||||
Sursa primara a controalelor: `docs\cercetare\inventar_controale_formulare.md` §4 si
|
||||
`docs\S1_inventar_campuri_formular_unificat.md` (randurile "Pliat — alte date"), reconfirmate aici
|
||||
direct pe `COMUN\clase\ferestre_cere_date.vc2` (clasa `frm_alte_date`, `2219-3353`; garda de validare
|
||||
`inainte_de_do_termin` la `3044-3103`, `Init` la `3105-3208`).
|
||||
|
||||
### 1.1 Delegat / transport
|
||||
|
||||
| Control | Caption real | `fisier:linie` (ADD OBJECT) | Vizibilitate | Ruta de scriere (din rute_scriere_antet.md / S1) |
|
||||
|---|---|---|---|---|
|
||||
| `Ct_clb_delegat` | "Delegat" | `ferestre_cere_date.vc2:2553` (container `ct_clb_cautare`-like, `caution=do_cauta_delegat`) | pliat, mereu editabil pe document nou; **eliminat cu totul** cand `poDate.eProforma=1` (`:3192`) | pe loc, `modifica_date_factura` (`V_ID_DELEGAT`), neconditionat — `modifica_date_factura_parametri.md:17` |
|
||||
| `Ct_clb_masina` | "Masina" | `:2569` | idem, eliminat pe proforma (`:3193`) | `V_ID_MASINA`, neconditionat — `:18` |
|
||||
| `Ct_clb_agent` | "Agent" | `:2537` | idem, **nu e eliminat pe proforma** (nu apare in cascada `RemoveObject` `:3192-3201`) | `V_ID_AGENT`, neconditionat — `:19` |
|
||||
| `Clb_dataora_exp` | "Data si ora expedierii" | `:2443` | idem, nu eliminat pe proforma; **validat obligatoriu** in `inainte_de_do_termin` (`:3062-3067`, `Case Empty(Nvl(poDate.dataora_exp,{})) And poDate.eProforma = 0`) | `V_DATAORA_EXP`, neconditionat — `:20` |
|
||||
| `But_modifica1` | (icon, `caction` implicit -> `do_modifica`) | `:2343` | actiune, nu camp — deschide `nom_parteneri_modifica` pe delegatul curent (`ferestre_cere_date.vc2:2925-2935`) | — |
|
||||
|
||||
**Validarea de grup** (`inainte_de_do_termin`, `:3055-3060`): delegatul e obligatoriu doar cand
|
||||
`gcNumeProgram = [ROAFACTURARE]` **si** documentul nu e proforma sau bon fiscal
|
||||
(`!(poDate.eProforma = 1 OR poDate.eBonFiscal = 1)`) — nuanta care trebuie pastrata identic in
|
||||
formularul unificat, nu doar copiata "delegat obligatoriu".
|
||||
|
||||
### 1.2 Incasare (grupul cel mai mare, vezi si sectiunile 3-4)
|
||||
|
||||
| Control | Caption real / eticheta dinamica | `fisier:linie` | Vizibil cand |
|
||||
|---|---|---|---|
|
||||
| `opt_incasat` (radiogrup 4 optiuni) | "Fara incasare" / "Chitanta" / "Bon fiscal" / "POS Card" | `:2638-2685` (default `Value=1`) | mereu, dar **intreg containerul incasare** dispare pe proforma (`:3140-3143`, `Thisform.opt_incasat.Visible = .F.`, doar pe ramura non-proforma exista oricum) si pe `gcNumeProgram=[ROAGEST]` sau `!Between(poDate.tip,1,4)` |
|
||||
| `Cb_casa` | "Casa" -> "Banca POS" pe POS (`:2967` `_lbbase1.Caption='Banca POS'`) | `:2362` | ascuns doar la `opt_incasat=1` |
|
||||
| `Clb_serie_chit` | "Serie chitanta" | `:2503` | vizibil **doar** la Chitanta (`Value=2`) |
|
||||
| `Clb_nrchit` | "Nr. chitanta" -> "Nr. bon" pe Bon fiscal/POS | `:2480` | ascuns doar la `Value=1` |
|
||||
| `Clb_incasat` | "Incasat" | `:2461` | ascuns doar la `Value=1` |
|
||||
| `cmdModificaBon` | icon, `caction=do_modifica_bon` | `:2526` | vizibil **doar** la Bon fiscal (`Value=3`) |
|
||||
| `chkPOS` | "POS" | `:2409` | vizibil la Bon fiscal (`Value=3`, bifabil) **si** la POS (`Value=4`, fortat `.T.` si ascuns — `:2845` `thisform.chkPOS.Value=1` apoi `.Visible=.F.`) |
|
||||
| `chkDetaliat` | "Detaliat" | `:2396` | vizibil doar la Bon fiscal |
|
||||
| `cboTipFactura` | combo, langa "Tip factura" | `:2378` | mereu (langa incasare), scrie `poDate.tip_saft` |
|
||||
|
||||
Masina de stari completa e la sectiunea 3-4. **Eticheta "Casa"/"Banca POS" si vizibilitatea lui
|
||||
`chkPOS`/`chkDetaliat` fac parte din aceeasi metoda unica** (`actualizeaza_tipincasare`) — nu sunt
|
||||
conditii separate de refacut, sunt randuri din acelasi `Do Case`.
|
||||
|
||||
### 1.3 Adresa de facturare
|
||||
|
||||
| Control | Caption | `fisier:linie` | Vizibilitate |
|
||||
|---|---|---|---|
|
||||
| `clb_adresa_facturare` | "Adresa facturare" | `:2422` | pliat, mereu editabil; **nu** eliminat pe proforma (absent din cascada `:3192-3201`) |
|
||||
| `do_cauta_adresa` (metoda, nu control separat) | — | `:2911-2926` | cauta pe `poDate.id_client`, seteaza `poDate.adresa_facturare`/`poDate.id_facturare` |
|
||||
|
||||
Ruta de scriere: `V_ID_FACTURARE`, neconditionat — `modifica_date_factura_parametri.md:21`.
|
||||
|
||||
### 1.4 Text aditional
|
||||
|
||||
| Control | Caption | `fisier:linie` | Vizibilitate |
|
||||
|---|---|---|---|
|
||||
| `Ed_tx_simplu1` | "Text aditional (max. 1000 caractere)" | `:2585` | pliat, mereu editabil; nu eliminat pe proforma — dimpotriva, **pe proforma ramane singurul grup activ**, formularul se redimensioneaza in jurul lui (`:3184-3207`) |
|
||||
| `_checkbox1` ("Listare detaliata") | "Listare detaliata" | `:3002-3011` | ADD OBJECT separat, `ControlSource=poDate.nListareDetaliata` — **nu e in cascada celor patru grupuri din B**, e propriul lui control, langa text aditional in layout, dar conceptual apartine grupului de raportare (I-bis) alaturi de `tip_saft`/`efactura`, nu grupului "text aditional" |
|
||||
|
||||
Ruta: `V_TEXT_ADITIONAL`, neconditionat, **fara** `NVL` — `modifica_date_factura_parametri.md:23`.
|
||||
|
||||
**Observatie structurala pentru mockup**: `_checkbox1` (listare detaliata) e cablat separat de
|
||||
`chkDetaliat` din grupul incasare (control diferit, aceeasi semantica probabila — ambiguitate deja
|
||||
semnalata in S1, randul 57, neinchisa aici). Formularul unificat trebuie sa aleaga **unul singur**
|
||||
dintre cele doua controale existente, nu sa le porteze pe amandoua.
|
||||
|
||||
---
|
||||
|
||||
## 2. Analiticele coborate din antet (decizia 8, nuantata de decizia 26)
|
||||
|
||||
Controale: `Ct_clb_venchelt` ("Venit / cheltuiala"), `Ct_clb_sectie` ("Sectie"), `Ct_clb_responsabil`
|
||||
("Responsabil"), `Ct_clb_lucrare` ("Lucrare") — toate patru container `ct_clb_cautare` (subclasa
|
||||
`caut_ora.vcx`), azi pe `frm_date_factura`/`frm_date_aviz`, **nu** pe `frm_alte_date`
|
||||
(`inventar_controale_formulare.md` §3; S1 randurile 39-42). Live in sectiunea I a planului ca grup
|
||||
"Pliat — analitice", distinct de cele patru grupuri ale lui `frm_alte_date` — dar aceeasi sectiune
|
||||
pliata a formularului unificat, conform textului S3b ("peste ele coboara analiticele din antet").
|
||||
|
||||
### 2.1 Ce inseamna "afisare read-only" mecanic
|
||||
|
||||
Sursa: `docs\cercetare\rute_scriere_antet.md` §1 — verdict deja stabilit, **DA** pentru
|
||||
`ID_SECTIE`/`ID_RESPONSABIL`/`ID_LUCRARE` printr-o cale noua (`frm_facturi.do_editare_factura` ->
|
||||
`frm_modific2024`, perimetrul #6), **neconfirmat** pentru `ID_VENCHELT`. Pentru #13, indiferent de
|
||||
raspunsul acelei rezerve, decizia 26 e explicita: **zero editare din formularul unificat**, oricare ar
|
||||
fi ruta reala din Oracle.
|
||||
|
||||
**Mecanismul de blocare vizuala, verificat pe cod, cu un gap real**:
|
||||
`ct_clb_cautare.do_dezactiveaza()` (`caut_ora.vc2:800-806`):
|
||||
```
|
||||
PROCEDURE do_dezactiveaza
|
||||
This.lactiv = .F.
|
||||
This.img_cautare.Visible = .F.
|
||||
This.ctooltip = This.clb_tx_cautare.text_simplu1.ToolTipText
|
||||
This.clb_tx_cautare.text_simplu1.ToolTipText = []
|
||||
This.clb_tx_cautare.text_simplu1.Refresh()
|
||||
ENDPROC
|
||||
```
|
||||
`This.lactiv` gateaza **doar** deschiderea dialogului de cautare — `DblClick` (`:822-826`),
|
||||
`KeyPress` pe Enter/Tab/F7 (`:829-836`), `img_cautare.Click` (`:840-843`) — toate testeaza
|
||||
`This.Parent(.Parent).lactiv` inainte sa cheme `do_apeleaza()`. **Nu seteaza `ReadOnly` pe
|
||||
`clb_tx_cautare.text_simplu1`** — textbox-ul bindat (`ControlSource=This.cvar_afisata`, setat in
|
||||
`Init`, `caut_ora.vc2:814-817`) ramane direct tastabil. Pentru un camp cu adevarat read-only (decizia
|
||||
26 cere explicit "nu se editeaza"), `do_dezactiveaza()` trebuie **completat**, nu doar apelat: se
|
||||
adauga `This.clb_tx_cautare.text_simplu1.ReadOnly = .T.` (si eventual `.TabStop = .F.`, ca sa nu
|
||||
primeasca focus deloc) langa apelul existent — o linie noua, in tiparul deja recomandat de plan
|
||||
(sectiunea I: "singura reteta completa e `clb_tx_data.dezactiveaza()`... se generalizeaza de la ea").
|
||||
|
||||
**Consecinta pentru "un singur buton deschide tot" (decizia 9)**: `do_dezactiveaza()` se apeleaza **o
|
||||
singura data**, la constructia sectiunii, **si nu se mai cheama niciodata `do_activeaza()`** pentru
|
||||
aceste patru controale — nici cand `but_modifica` deblocheaza restul antetului. E o exceptie
|
||||
deliberata de la "un singur control comanda toata protectia antetului" (plan I:843-853), simetrica cu
|
||||
exceptia deja acceptata pentru grupurile B/C la decizia 25 (sectiunea 5 de mai jos).
|
||||
|
||||
### 2.2 Eticheta / tooltip
|
||||
|
||||
Nicio propunere de text nu exista inca in materialele citite — de scris la implementare. Recomandare
|
||||
minima, in stilul deja folosit in codebase (ex. `Ct_clb_gestiune_init.ToolTipText`, un citat complet
|
||||
la `inventar_controale_formulare.md:93-95`, deci precedent de lungime/ton acceptat): eticheta ramane
|
||||
neschimbata ("Venit / cheltuiala"/"Sectie"/"Responsabil"/"Lucrare"), `ToolTipText` nou de tipul
|
||||
*"Se editeaza din Editare factura (articole, cantitati, preturi) — buton disponibil pe fiecare linie
|
||||
a notei contabile"*, ca sa nu para camp stricat. Text exact — **de decis de Marius**, vezi sectiunea 9.
|
||||
|
||||
### 2.3 Nivel antet vs. nivel linie — de retinut la afisare
|
||||
|
||||
`rute_scriere_antet.md` §1.4: editarea reala prin #6 e **pe linie de nota**, nu pe antet — un
|
||||
document poate ajunge cu sectii diferite pe linii diferite dupa editare (imposibil la emitere). Cand
|
||||
formularul unificat afiseaza analiticele "cu valoarea de antet" (cum spune decizia 26), afiseaza de
|
||||
fapt **o singura valoare reprezentativa** (probabil prima linie sau variabila de sesiune retinuta la
|
||||
emitere), nu o agregare a liniilor — daca liniile diverg dupa o editare #6, afisarea din #13 devine
|
||||
"aproximativa, nu autoritara". Nu e o contradictie de rezolvat aici (decizia 26 exista tocmai ca sa
|
||||
evite ca #13 sa scrie peste diferentierea de linie), dar merita un rand explicit in criteriul de
|
||||
"gata" (sectiunea 10): afisarea trebuie sa spuna clar ce valoare arata, nu sa pretinda ca e valoarea
|
||||
unica a documentului daca liniile difera.
|
||||
|
||||
---
|
||||
|
||||
## 3. `actualizeaza_tipincasare`
|
||||
|
||||
Definita la `COMUN\clase\ferestre_cere_date.vc2:2698-2856`, metoda proprie a clasei `frm_alte_date`.
|
||||
Comuta vizibilitatea intregului subgrup de incasare pe `This.opt_incasat.Value` (patru ramuri, tabel
|
||||
complet la sectiunea 1.2 si in `inventar_controale_formulare.md:130-135`) **si**, in aceeasi metoda,
|
||||
aloca/dezaloca numerele — cele doua responsabilitati (UI si alocare) sunt **impletite in acelasi
|
||||
`Do Case`**, nu separate. Fiecare ramura incepe prin a dezaloca defensiv celelalte trei tipuri de
|
||||
numar, apoi aloca pe cel curent daca e cazul:
|
||||
|
||||
| `opt_incasat.Value` | Dezaloca la intrare | Aloca | Alte efecte |
|
||||
|---|---|---|---|
|
||||
| 1 (Fara incasare) | 16, 3, 26 | — | `poDate.incasat=0`, `poDate.nr_incasare=0` |
|
||||
| 2 (Chitanta) | 3, 16, 26 | `creeaza_cursor_serii(16)` + `clb_serie_chit.genereazaNumar()` (`:2783`) — doar daca `nRezultatSerii=0` (nu realoca daca seria era deja creata) | `poDate.incasat=poDate.totalctva` |
|
||||
| 3 (Bon fiscal) | 3, 16, 26 | `do_aloca_nr_bon([CLICK])` (`:2807`) -> cod 3 | idem, plus `tip_saft` poate deveni 751 daca `gnEFactura_tip_saft_bf` e setat |
|
||||
| 4 (POS/Card) | 3, 16, 26 | `do_aloca_nr_pos([CLICK])` (`:2844`) -> cod 26 | idem, `chkPOS.Value` fortat 1 si ascuns |
|
||||
|
||||
**Ce depinde de ea**: `opt_incasat.Click` (`:3250-3251`) **si** `opt_incasat.ProgrammaticChange`
|
||||
(`:3351-3352`) — vezi sectiunea 4, e disjunctia care conteaza. Nu are alti apelanti directi in
|
||||
`ferestre_cere_date.vc2` (cautare `vfp_symbols.ps1 -Grep actualizeaza_tipincasare -CodeOnly`
|
||||
recomandata la implementare pentru confirmare exhaustiva pe tot proiectul — nu rulata aici din motive
|
||||
de buget, dar apelantul extern deja cunoscut e citat mai jos, la 3.1).
|
||||
|
||||
### 3.1 Apel extern, cu context important
|
||||
|
||||
`COMUN\clase\ofacturare.vc2:14919-14926` (`frm_facturare_articole(2).inainte_de_do_termin`, inainte
|
||||
de `ofrmdatesupl.Show(1)`):
|
||||
```
|
||||
ofrmdatesupl = Createobject("frm_alte_date")
|
||||
ofrmdatesupl.nnrbon = poDate.nract
|
||||
If (poDate.eBonFiscal = 1)
|
||||
With ofrmdatesupl
|
||||
.opt_incasat.Value = 3 && INCASARE CU BON FISCAL
|
||||
.actualizeaza_tipincasare() && apel EXPLICIT, dupa .Value=
|
||||
.opt_incasat.option1.TabStop = .F.
|
||||
...
|
||||
```
|
||||
Apelantul seteaza `.Value=3` **si** cheama explicit `actualizeaza_tipincasare()` imediat dupa — ceea
|
||||
ce, coroborat cu descoperirea de la sectiunea 4 (`.Value=` deja declanseaza `ProgrammaticChange` ->
|
||||
aceeasi metoda), inseamna ca metoda ruleaza de doua ori la acest apel. Nu produce o eroare vizibila
|
||||
(fiecare ramura e idempotenta: dezaloca ce era alocat, realoca acelasi tip), dar confirma ca autorul
|
||||
codului nu s-a bazat exclusiv pe evenimentul `ProgrammaticChange` — semn ca mecanismul lui exact
|
||||
(cand se declanseaza, cand nu) n-a fost niciodata documentat explicit, doar "acoperit din ambele
|
||||
parti". **Portarea "ca atare" trebuie sa pastreze acest apel dublu identic**, nu sa-l "curete" ca
|
||||
redundant — eliminarea unuia dintre cele doua declansatoare ar putea rupe o presupunere neverificata
|
||||
in alta parte a codului.
|
||||
|
||||
### 3.2 "Se muta ca atare" — ce inseamna concret
|
||||
|
||||
Corpul metodei (`:2698-2856`) nu are nicio dependenta de fereastra-container (`Thisform.*` peste
|
||||
tot, nu `This.Parent.*`) — se poate muta **byte-cu-byte** pe formularul unificat, cu conditia ca
|
||||
toate cele noua controale referite (`opt_incasat`, `cb_casa`, `clb_serie_chit`, `clb_nrchit`,
|
||||
`clb_incasat`, `_shape3`, `lb_simplu1`, `cmdModificaBon`, `chkPOS`, `chkDetaliat`, `cboTipFactura`)
|
||||
sa existe cu **exact aceleasi nume** pe noul formular, in acelasi container logic (`Thisform`, nu un
|
||||
subpanou cu alt scope) — altfel referintele nekvalificate `Thisform.xxx` pica silentios pe obiect
|
||||
inexistent. Aceasta e o constrangere de implementare directa: **sectiunea pliata trebuie sa fie
|
||||
container-transparenta** pentru aceasta metoda (fie pusa direct pe `Thisform`, fie metoda insasi
|
||||
adaptata sa foloseasca `This.Parent`-ul corect) — nu poate fi mutata intr-un obiect-container separat
|
||||
fara o trecere explicita de `Thisform.xxx -> This.Parent.xxx` pe toate liniile.
|
||||
|
||||
---
|
||||
|
||||
## 4. Alocarea si dezalocarea numerelor — evenimentele exacte
|
||||
|
||||
### 4.1 Evenimente de alocare (azi)
|
||||
|
||||
1. **`opt_incasat.Click`** (`:3250-3251`) — interactiune reala a utilizatorului pe radiogrup.
|
||||
2. **`opt_incasat.ProgrammaticChange`** (`:3351-3352`) — **descoperire centrala a acestui raport**,
|
||||
negasita in materialul de plan sau in cele patru rapoarte de referinta: in VFP, orice atribuire
|
||||
`.Value = n` facuta din cod (nu de utilizator) declanseaza `ProgrammaticChange`, nu `Click`. Clasa
|
||||
are ambele metode cablate pe **acelasi** apel (`actualizeaza_tipincasare()`), deci **orice loc din
|
||||
cod care scrie `opt_incasat.Value = ...` aloca/dealoca numere**, indiferent de intentie.
|
||||
3. **Chiar `Init`-ul clasei** foloseste acest canal: `:3150`, `Thisform.opt_incasat.Value = 2` — ruleaza
|
||||
**doar daca** `poDate.incasat <> 0` la momentul deschiderii. Verificat pe cod (nu presupus) ca
|
||||
`poDate.incasat` poate fi nenul **inainte** ca `frm_alte_date` sa se deschida, prin cel putin patru
|
||||
cai reale: `ofacturare_stoc.prg:535`, `oproceduri_facturare.prg:1186`,
|
||||
`ofacturare_comun.vc2:4066`, `ofacturare_comun.vc2:4260` (toate patru scriu `poDate.incasat =
|
||||
incasat`, parametru primit de la apelant — flux de copiere/reluare a unui document cu incasare deja
|
||||
stabilita). **Concluzie verificata**: azi, deschiderea dialogului `frm_alte_date` pe un astfel de
|
||||
document **aloca deja un numar de chitanta la Init, inainte ca userul sa apese orice**. Nu e o
|
||||
ipoteza — e mecanismul descris la sectiunea 3, declansat de linia 3150.
|
||||
4. `do_modifica_bon`/`do_modifica_pos` (`:3001-3022`) — dezaloca explicit + realoca, la apasarea
|
||||
butonului "Modifica bon"/echivalent POS.
|
||||
5. `do_aloca_nr_bon`/`do_aloca_nr_pos` (`:2864-2907`) — chemate din interiorul lui
|
||||
`actualizeaza_tipincasare` (ramurile 3 si 4), nu independent.
|
||||
|
||||
### 4.2 Evenimente de dezalocare (azi)
|
||||
|
||||
1. **In interiorul `actualizeaza_tipincasare`** — fiecare ramura dezaloca defensiv celelalte trei
|
||||
tipuri **inainte** de a (re)aloca pe cel ales (tabelul de la sectiunea 3). Efect: comutarea intre
|
||||
optiuni, in cadrul aceleiasi sesiuni de dialog, e curata — nu ramane niciun numar orfan din
|
||||
optiunea anterioara.
|
||||
2. **`inainte_de_do_renunt`** (`:3023-3043`) — la anulare (buton Renunt / ESC pe dialogul modal):
|
||||
```
|
||||
inainte_de_do_renunt (ferestre_cere_date.vc2:3025-3030)
|
||||
If Type('poGeneratorNumere') = 'O'
|
||||
poGeneratorNumere.dezaloca_numar(16) && chitanta
|
||||
poGeneratorNumere.dezaloca_numar(3) && bon fiscal
|
||||
...
|
||||
```
|
||||
**Gap real, preexistent, verificat pe cod**: acest bloc dezaloca 16 (chitanta) si 3 (bon fiscal),
|
||||
dar **nu 26 (POS)** — nici direct, nici in blocul urmator (`:3033-3035`, care dezaloca doar 16 din
|
||||
nou, conditionat). Daca utilizatorul alege POS/Card si apoi anuleaza dialogul, numarul POS alocat
|
||||
**ramane alocat**, nedezalocat. Nu e introdus de S3b — exista deja in codul de azi — dar merita
|
||||
semnalat explicit ca risc mostenit (sectiunea 9), pentru ca S3b il "muta ca atare" (plan, textul
|
||||
povestii), deci il si multiplica pe orice cale noua prin care sectiunea pliata s-ar putea
|
||||
"renunta" fara sa fie un dialog modal cu propriul buton Renunt.
|
||||
3. **`Init` pe eroare Oracle** (`:3159`) — `poGeneratorNumere.dezaloca_numar(5)` (factura) daca
|
||||
interogarea `v_nom_casa` esueaza, cu `Return` imediat dupa (linie explicita, spre deosebire de
|
||||
bug-ul #16 din `s3_portare_antet.md` §6, unde nu exista `Return` echivalent). Semnalat, neurmarit
|
||||
mai departe aici — acelasi risc de arhitectura ca #16, dar pe alt fisier.
|
||||
|
||||
### 4.3 Riscul central pentru formularul unificat, si recomandarea
|
||||
|
||||
**Problema mecanica exacta**: in fluxul de azi, `frm_alte_date` e un dialog modal, instantiat **o
|
||||
singura data** per emitere (`ofacturare.vc2:14921`), asa ca `Init`-ul lui (si declansarea eventuala de
|
||||
la linia 3150) ruleaza **o singura data**. In formularul unificat, grupul de incasare devine o
|
||||
sub-sectiune a unui panou pliabil, pe **acelasi obiect de formular persistent** — daca implementarea
|
||||
naiva re-populeaza `opt_incasat.Value` din `poDate` de fiecare data cand sectiunea se depliaza (ex.
|
||||
"la fiecare `Show`/`Visible=.T.` al panoului, resincronizeaza controalele cu starea curenta a lui
|
||||
`poDate`"), **fiecare depliere ar re-declansa `ProgrammaticChange` -> `actualizeaza_tipincasare()` ->
|
||||
dezaloca tot + realoca pe optiunea curenta** — incalcand direct criteriul din S3b ("deschiderea si
|
||||
inchiderea formularului fara a atinge zona de incasare nu aloca si nu dezaloca niciun numar").
|
||||
|
||||
**Recomandare concreta**: populeaza `opt_incasat.Value` din `poDate` **o singura data**, la
|
||||
constructia sectiunii de antet a documentului (echivalentul lui `Init` de azi, care ruleaza o data per
|
||||
sesiune de emitere/editare) — nu la fiecare toggle de pliere/depliere. Plierea/deplierea trebuie sa
|
||||
fie **strict** un toggle de `Visible`/`Height` pe containerul vizual, fara nicio atingere a
|
||||
proprietatii `.Value` a lui `opt_incasat` sau a oricarui alt control cu `ProgrammaticChange` cablat pe
|
||||
alocare. Aceasta e exact genul de regula care trebuie verificata explicit la implementare (criteriu de
|
||||
"gata" concret la sectiunea 10), nu presupusa.
|
||||
|
||||
---
|
||||
|
||||
## 5. Grupul de incasare pe document deja emis (decizia 25)
|
||||
|
||||
**Ce spune decizia**: orice camp schimbat din grupul C (incasare) pe un document deja emis
|
||||
"marcheaza documentul pentru regenerare" — dar regenerarea nu exista in etapa I, deci **planul tine
|
||||
campurile blocate**, cu explicatie la hover, pana la etapa II (plan `:192-201`, `:1847-1848`).
|
||||
`rute_scriere_antet.md` §3 confirma independent ca nu exista nicio cale directa de scriere pentru
|
||||
grupul C dupa emitere (verdict "NU" pentru toate controalele, "PARTIAL neconfirmat" doar pentru o
|
||||
cale indirecta prin editarea notei — vezi §3.2 acolo, ramane deschisa).
|
||||
|
||||
### 5.1 Mecanic: pe ce proprietate, pe ce conditie
|
||||
|
||||
Decizia 9 stabileste ca `but_modifica` deblocheaza **tot** antetul, fara exceptie de camp — dar
|
||||
decizia 25 taie o exceptie explicita peste asta pentru grupurile B si C. Nu exista inca (verificat,
|
||||
`rute_scriere_antet.md` §2-3) un mecanism de blocare per-grup separat de `but_modifica`; trebuie
|
||||
construit nou, dar dupa un tipar deja folosit in codebase pentru exact intrebarea "acest document are
|
||||
deja un numar, sau e nou": `chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))`
|
||||
(`ofacturare_comun.vc2:5736-5750`, citat deja in plan I:839 si S1 randul 60). Decizia 9 **abandoneaza**
|
||||
acest test ca poarta pe cele patru bife de identitate (serie/numar/data/scadenta) — dar testul insusi
|
||||
ramane exact instrumentul potrivit pentru o alta intrebare: "documentul e nou sau deja emis?", care e
|
||||
tocmai conditia ceruta de decizia 25 pentru grupurile B/C.
|
||||
|
||||
**Recomandare mecanica**: la construirea antetului, se calculeaza o singura data un flag (ex.
|
||||
`lDocumentExistent = !EMPTY(NVL(poRec.numar_act,0))` sau echivalentul lui pe obiectul `poDate`/`poRec`
|
||||
folosit de formularul unificat — de confirmat la implementare care obiect poarta `numar_act` in noua
|
||||
arhitectura, pentru ca `poRec` era specific lui `frm_modifica_factura`, perimetrul #6). Controalele
|
||||
grupului C (`opt_incasat` si tot ce atarna de el din tabelul sectiunii 1.2) primesc `Enabled = .F.`
|
||||
**neconditionat de starea lui `but_modifica`** cand `lDocumentExistent = .T.`, in etapa I. Cand
|
||||
`but_modifica` deblocheaza restul antetului, acest grup **ramane** dezactivat — e o exceptie fixa, nu
|
||||
una care se recalculeaza la fiecare click pe `but_modifica`.
|
||||
|
||||
**Grupul B** (tip document, valuta, zi curs, client, sursa, gestiune sursa, politica de preturi) are
|
||||
aceeasi tratare — dar acele controale traiesc azi in `frm_date_factura`/`frm_date_aviz`
|
||||
(sectiunea I a planului, nu sectiunea B de aici), deci blocarea lor nu e in perimetrul S3b propriu-zis;
|
||||
se semnaleaza aici doar pentru consecventa mecanismului (acelasi flag, acelasi tipar de `Enabled=.F.`
|
||||
peste orice stare a lui `but_modifica`).
|
||||
|
||||
### 5.2 La emitere, se comporta ca azi
|
||||
|
||||
Pentru un document **nou** (nu inca emis), `lDocumentExistent = .F.`, grupul C e activ normal — se
|
||||
comporta identic cu `frm_alte_date` de azi, cu masina de stari intreaga din sectiunile 3-4. Nimic nu
|
||||
se schimba pe calea de emitere initiala; blocarea se aplica **doar** cand formularul unificat deschide
|
||||
un document deja scris in `VANZARI` (calea de editare, nu de creare).
|
||||
|
||||
### 5.3 Tooltip / explicatie la hover
|
||||
|
||||
Ca la sectiunea 2.2, textul exact nu exista inca — de propus la implementare, in tonul deja folosit in
|
||||
codebase. Continut minim necesar: sa spuna ca modificarea incasarii pe un document emis nu e inca
|
||||
posibila din acest formular ("disponibil dupa ce regenerarea documentului e implementata" sau
|
||||
echivalent), nu doar "camp dezactivat" fara explicatie — plan `:198-200` cere explicit "cu explicatie
|
||||
la hover".
|
||||
|
||||
---
|
||||
|
||||
## 6. Mecanismul de pliere — nu exista, se construieste nou dupa un tipar existent
|
||||
|
||||
**Cautare directa in prototip**: `vfp_symbols.ps1 -Grep` (si grep text simplu) pe "plia|colaps|
|
||||
collapse|expand|acordeon|accordion" in `COMUN\clase\ofacturare.vc2` (unde traieste
|
||||
`frm_facturare_articole2`, `:15741-19355`) — **zero potriviri**. Prototipul are controalele de antet
|
||||
montate direct pe formular (confirmat deja de `s3_portare_antet.md` §3 si de
|
||||
`inventar_controale_formulare.md` §2), dar **niciun mecanism de show/hide de sectiune** — nu exista
|
||||
nici macar un buton candidat.
|
||||
|
||||
**Cel mai apropiat tipar real din suita**: `frm_modific2024.afiseaza_rulaje`
|
||||
(`COMUN\clase\omodificari.vc2:13169-13199`, perimetrul #6, doar citit aici). Cod complet:
|
||||
```
|
||||
PROCEDURE afiseaza_rulaje
|
||||
* COLAPSEAZA SAU MARESTE PAGEFRAME-UL RULAJE
|
||||
lcPicture = this.but_afiseaza_rulaje.Picture
|
||||
...
|
||||
llAfiseazaRulaje = 'sus'$m.lcPicture
|
||||
IF m.llAfiseazaRulaje
|
||||
lcPicture2 = STRTRAN(m.lcPicture,'sus','jos') ...
|
||||
ELSE
|
||||
lcPicture2 = STRTRAN(m.lcPicture,'jos','sus') ...
|
||||
ENDIF
|
||||
this.but_afiseaza_rulaje.Picture = m.lcPicture2
|
||||
...
|
||||
this.pgfArticole.Visible = m.llAfiseazaRulaje
|
||||
this.but_copiazaR.Visible = m.llAfiseazaRulaje
|
||||
this.but_modificaR.Visible = m.llAfiseazaRulaje
|
||||
this.but_stergeR.Visible = m.llAfiseazaRulaje
|
||||
thisform.resize_grid1()
|
||||
ENDPROC
|
||||
```
|
||||
Trei observatii directe:
|
||||
1. **Foloseste exact acelasi idiom sus/jos** deja validat si documentat pentru `but_modifica`/
|
||||
`but_salveaza` (decizia 9, `docs\cercetare\buton_comutator_picture.md`, citat in plan `:63-75`):
|
||||
citeste starea curenta din numele fisierului `Picture` (contine "sus" sau "jos"), calculeaza
|
||||
perechea opusa prin `STRTRAN`, rescrie **`Picture` + `cPictureDown` + `cPictureUp` impreuna** —
|
||||
aceeasi precautie semnalata in plan ("numai `.Picture` nu ajunge: hover-ul standard din
|
||||
`buton.MouseEnter`/`MouseLeave` rescrie `Picture` din `cPictureUp`/`cPictureDown`").
|
||||
2. **Nu e o clasa reutilizabila, e cod ad-hoc per caz** — toggle direct pe `.Visible` al unei liste
|
||||
fixe de controale, plus un apel manual de reflow (`resize_grid1()`, metoda proprie a formularului,
|
||||
nu un mecanism generic). Nu exista in nicio biblioteca de clase din `COMUN` (`_baza.vcx`, etc.,
|
||||
cautare separata, zero potriviri pentru o clasa "panou pliabil"/"expander"/"accordion") un
|
||||
container gata facut de pliere. **S3b trebuie sa scrie cod nou**, dupa acest tipar, nu sa
|
||||
instantieze o clasa existenta.
|
||||
3. **E in perimetrul #6, doar citire** — poate fi copiat ca *idee*/tipar la implementare, dar
|
||||
`omodificari.vc2` insusi nu se atinge cat timp #6 e in lucru (decizia 30).
|
||||
|
||||
### 6.1 Ce trebuie sa scrie S3b, concret
|
||||
|
||||
- **Un buton comutator per sectiune pliata** (sau unul singur pentru toata zona "alte date +
|
||||
analitice", de decis la implementare — plan I nu specifica granularitatea), cu imaginile
|
||||
sus/jos deja existente in `COMUN\grafice` (aceleasi perechi folosite pentru `but_afiseaza_rulaje`,
|
||||
de identificat exact numele fisierelor la implementare — nu verificate aici, doar tiparul de
|
||||
mecanism).
|
||||
- **Toggle de `.Visible`/`.Height`** pe grupul de controale al sectiunii (delegat/transport, incasare,
|
||||
adresa, text aditional, analitice), plus un apel de reflow echivalent lui `resize_grid1()` — pentru
|
||||
ca restul formularului (butoane, alte sectiuni de mai jos) sa nu ramana cu goluri sau suprapuneri
|
||||
cand sectiunea se pliaza/depliaza. VFP nu reflow-eaza automat un layout absolut-pozitionat (`Left`/
|
||||
`Top` fixe pe fiecare control) — de asta si tiparul din `frm_date_factura.Init` (citat in
|
||||
`s3_portare_antet.md` §5, "masoara inaltimile containerelor de antet intr-un array `laPozitii`") cat
|
||||
si `afiseaza_rulaje` fac manual acest calcul; nu exista un layout manager automat de reutilizat.
|
||||
- **Populare o singura data** a starii initiale (sectiunea 4.3) — separat de toggle-ul de vizibilitate,
|
||||
ca sa nu retrigger-eze `ProgrammaticChange`.
|
||||
- **Intrebare deschisa, semnalata deja in `s3_portare_antet.md` §5** (nu redusa aici): daca lookup-urile
|
||||
Oracle ale lui `frm_alte_date.Init` (delegat/masina ultima factura, cursorul `v_nom_casa`) raman
|
||||
**eager** (rulate la construirea formularului, ca azi) sau devin **lazy** (amanate pana la prima
|
||||
depliere a sectiunii, ca sa nu incarce round-trip-uri Oracle inutile cand userul nu ajunge niciodata
|
||||
acolo). Ambele optiuni sunt compatibile cu criteriul "deschiderea/inchiderea nu aloca numere" **doar
|
||||
daca** populate keeping in mind sectiunea 4.3 (populare o singura data, indiferent de varianta
|
||||
aleasa) — **de decis de Marius**, vezi sectiunea 9.
|
||||
|
||||
---
|
||||
|
||||
## 7. Ordinea de executie
|
||||
|
||||
Pasi care lasa suita functionala dupa fiecare — calea veche (`frm_alte_date` ca dialog separat) **nu
|
||||
se sterge** in etapa I, cf. textul povestii ("`frm_alte_date` nu se sterge cat timp calea veche mai e
|
||||
in uz").
|
||||
|
||||
1. **Construieste sectiunea vizuala pliata** (patru grupuri din sectiunea 1 + analiticele din
|
||||
sectiunea 2), fara nicio logica de alocare/validare inca — doar controale + toggle de
|
||||
Visible/Height (sectiunea 6). *Criteriu:* fiecare control existent in `frm_alte_date`/antetul de
|
||||
analitice apare o data, cu aceeasi eticheta, in sectiunea corecta a formularului unificat —
|
||||
verificare camp-cu-camp fata de tabelele din sectiunile 1-2 de aici.
|
||||
2. **Porteaza `actualizeaza_tipincasare` ca atare** (sectiunea 3), cu toate cele noua referinte
|
||||
`Thisform.xxx` rezolvate corect in noul container (sectiunea 3.2). *Criteriu:* alegerea fiecarei
|
||||
optiuni din `opt_incasat` produce exact aceleasi `Visible`/etichete pe controalele dependente ca pe
|
||||
`frm_alte_date` de azi, verificabil headless (sectiunea 8).
|
||||
3. **Rezolva explicit riscul de la sectiunea 4.3** — populare unica a starii, toggle de pliere fara
|
||||
atingere de `.Value`. *Criteriu, verificabil headless:* pe un document de test cu `poDate.incasat<>0`
|
||||
la construirea formularului, se depliaza si se plie
|
||||
aza sectiunea de trei ori consecutiv fara a atinge
|
||||
niciun control din grup — `poGeneratorNumere` (cursorul lui de numere alocate) arata **acelasi**
|
||||
numar de chitanta la finalul celor trei toggle-uri ca la inceput, nu unul nou de fiecare data.
|
||||
4. **Porteaza delegat/transport si adresa de facturare** (`do_cauta_delegat`/`do_cauta_masina`/
|
||||
`do_cauta_agent`/`do_cauta_adresa`/`do_modifica`), cu validarea din `inainte_de_do_termin`
|
||||
(sectiunea 1.1). *Criteriu:* fiecare cautare deschide acelasi dialog si scrie aceleasi proprietati
|
||||
pe `poDate` ca azi (comparatie directa, cod identic mutat).
|
||||
5. **Adauga analiticele read-only** (sectiunea 2), inclusiv completarea `do_dezactiveaza()` cu
|
||||
`ReadOnly=.T.` explicit. *Criteriu:* tastarea directa in oricare din cele patru campuri nu modifica
|
||||
valoarea afisata (verificare pe proprietate, headless).
|
||||
6. **Implementeaza blocarea grupului C pe document deja emis** (decizia 25, sectiunea 5), cu flag-ul
|
||||
de "document existent" si exceptia peste `but_modifica`. *Criteriu:* pe un document nou,
|
||||
`but_modifica` (cand exista, dupa S3/decizia 9) nu afecteaza grupul C (mereu activ); pe un document
|
||||
deja emis, grupul C ramane `Enabled=.F.` inainte **si** dupa apasarea lui `but_modifica`.
|
||||
7. **Verificare de regresie end-to-end**: emite acelasi document (numerar, chitanta, bon fiscal, POS —
|
||||
toate patru, nu doar unul) o data pe calea veche (`frm_alte_date` dialog) si o data pe formularul
|
||||
unificat, compara `poDate`-ul rezultat inainte de scriere in Oracle (aceleasi campuri de incasare,
|
||||
acelasi numar alocat) — tipar deja folosit in `s3_portare_antet.md` §7 pentru S3, reutilizat aici
|
||||
pe grupul mai mic al lui S3b.
|
||||
|
||||
*Depinde de S3* (planul o spune explicit) — pasii 4 si 6 presupun ca `but_modifica`/blocarea antetului
|
||||
(proiectata in S3, sectiunea I a planului) exista deja ca mecanism, nu doar ca design pe hartie.
|
||||
|
||||
---
|
||||
|
||||
## 8. Ce nu se poate testa headless
|
||||
|
||||
Precedent direct: `s3_portare_antet.md` §8, si memoria de proiect
|
||||
(`grid-coloane-nu-se-materializeaza-headless` — coloanele de grid nu se materializeaza sub `-A -T`,
|
||||
exista harness UI vizibil separat care le citeste corect).
|
||||
|
||||
**Se POATE testa headless** (contrar primei intuitii, pentru ca e logica pe proprietati, nu pictura pe
|
||||
ecran):
|
||||
- Toata masina de stari `actualizeaza_tipincasare` — `Createobject()` fara `.Show()` tot ruleaza
|
||||
`Init` si tot cabl eaza evenimentele; `thisform.opt_incasat.Value = n` declanseaza
|
||||
`ProgrammaticChange` identic cu UI vizibil sau nu. Verificarile de alocare/dezalocare de la pasul 3
|
||||
al sectiunii 7 sunt testabile 100% headless, pe proprietatile lui `poGeneratorNumere`.
|
||||
- Vizibilitatea controalelor dupa toggle (`.Visible`, `.Height`, existenta obiectului via `Type(...)`)
|
||||
— acelasi motiv, confirmat deja de `s3_portare_antet.md` §8 pentru mecanismul similar din `Init`-urile
|
||||
de antet.
|
||||
|
||||
**NU se poate testa headless**:
|
||||
- **Aspectul vizual real dupa pliere/depliere** (suprapuneri, goluri, pozitionarea corecta a
|
||||
controalelor de sub sectiune dupa reflow-ul manual, sectiunea 6.1) — layout absolut-pozitionat, fara
|
||||
reflow automat; verificarea ceruta e "arata bine pe ecran", nu o proprietate booleana — necesita
|
||||
harness UI vizibil (memoria de proiect citata mai sus) sau verificare manuala de Marius.
|
||||
- **Cele patru dialoguri modale** (`do_cauta_delegat`/`do_cauta_masina`/`do_cauta_agent`/
|
||||
`do_cauta_adresa`) — interactiunea reala de alegere dintr-o lista nu se simuleaza headless; se poate
|
||||
testa doar efectul **dupa** ce rezultatul cautarii e construit manual (tiparul deja folosit in
|
||||
proiect, citat in `s3_portare_antet.md` §8, pct. 1).
|
||||
- **Lookup-urile Oracle din `Init` (delegat/masina ultima factura, `v_nom_casa`)** — necesita conexiune
|
||||
Oracle reala (harnessul suporta asta prin `MARIUSM_AUTO`, dar tot nu e "headless" in sensul de
|
||||
"fara dependinte externe") si date de test care sa existe pentru clientul folosit.
|
||||
- **Butonul `cmdModificaBon`/`do_modifica_bon`** — deschide `viz_config_serii_complet WITH 3`
|
||||
(`oserii_numere.prg`), alt formular modal, aceeasi limitare ca dialogurile de cautare.
|
||||
|
||||
---
|
||||
|
||||
## 9. Riscuri si capcane
|
||||
|
||||
1. **Riscul central** (sectiunea 4.3): re-populare a starii `opt_incasat` la fiecare toggle de pliere
|
||||
ar re-declansa alocare/dezalocare, incalcand direct criteriul cerut de decizia 25/S3b. Tratat
|
||||
explicit ca pas 3 in ordinea de executie — **nu** de lasat "se rezolva de la sine prin arhitectura",
|
||||
pentru ca mecanismul de declansare (`ProgrammaticChange`) e usor de lovit accidental de orice cod
|
||||
ulterior care "resincronizeaza UI-ul cu modelul" intr-un loc gresit.
|
||||
2. **Gap preexistent, nu introdus de S3b, dar mostenit prin "muta ca atare"**: `inainte_de_do_renunt`
|
||||
(`:3025-3030`) nu dezaloca numarul POS (26) la anulare — doar chitanta (16) si bon fiscal (3). Daca
|
||||
formularul unificat introduce vreo cale noua de "renunta la document" care nu mapeaza exact pe acest
|
||||
cod, riscul se poate agrava (numar POS ramas alocat orfan, de fiecare data cand utilizatorul incepe
|
||||
un document cu POS si renunta). Recomandare: fie se corecteaza in trecere (nu e in perimetrul strict
|
||||
al portarii "ca atare", dar e o linie), fie se semnaleaza explicit ca bug cunoscut de preluat separat.
|
||||
3. **`_checkbox1`/"Listare detaliata" vs. `chkDetaliat`** (sectiunea 1.4) — doua controale cu semantica
|
||||
probabil identica, ambiguitate deja semnalata in S1 (randul 57), neinchisa aici. Formularul unificat
|
||||
trebuie sa aleaga unul singur; alegerea gresita (portarea amandurora ca doua campuri distincte)
|
||||
ar introduce un camp duplicat fara sens pentru utilizator.
|
||||
4. **`do_dezactiveaza()` insuficient pentru read-only real** (sectiunea 2.1) — daca implementarea
|
||||
apeleaza doar metoda existenta fara completarea de `ReadOnly`, decizia 26 nu e respectata mecanic:
|
||||
campul arata blocat (fara lupa) dar tot accepta taste.
|
||||
5. **Referinte `Thisform.xxx` nekvalificate** in `actualizeaza_tipincasare` (sectiunea 3.2) — orice
|
||||
decizie de a pune sectiunea pliata intr-un container/obiect separat (nu direct pe `Thisform`) rupe
|
||||
tacit aceste referinte, cu eroare de "obiect inexistent" la runtime, nu la compilare.
|
||||
6. **Dependinta pe S3**: `but_modifica` si testul "document existent" (sectiunea 5.1) nu exista inca
|
||||
nicaieri in cod — proiectate doar pe hartie in plan (sectiunea I). Pasii 4 si 6 din sectiunea 7 nu
|
||||
pot fi *implementati* complet inainte ca S3 sa livreze acel mecanism, doar proiectati.
|
||||
7. **Bug-ul de `sectie`** (`omodificari.vc2:13941`, semnalat deja in `rute_scriere_antet.md` §1.3 si
|
||||
in plan `:761-764`) — nu afecteaza direct S3b (analiticele sunt read-only aici, deci S3b nu scrie
|
||||
niciodata `id_sectie`), dar afecteaza increderea in ce se **afiseaza**: daca bug-ul e real, coloana
|
||||
`id_sectie` din Oracle poate sa nu reflecte eticheta aratata dupa o editare din #6. In afara
|
||||
perimetrului S3b de reparat (e in #6), dar relevant pentru cine citeste afisarea din #13 dupa o
|
||||
editare de la #6.
|
||||
|
||||
---
|
||||
|
||||
## 10. Criteriul de "gata", rescris verificabil
|
||||
|
||||
Textul din plan (`:1849-1852`) spune: *"un document cu incasare prin bon fiscal emis din formularul
|
||||
unificat produce aceleasi randuri si acelasi numar de bon ca pe calea veche; deschiderea si inchiderea
|
||||
formularului fara a atinge zona de incasare nu aloca si nu dezaloca niciun numar; iar pe un document
|
||||
emis analiticele se vad dar nu se pot edita."* Precizat mai jos, pe fiecare bucata:
|
||||
|
||||
1. **Paritate de emitere, pe toate patru tipurile de incasare, nu doar bon fiscal**: se emite acelasi
|
||||
document de test (client, articole, cantitati, preturi identice) o data pe calea veche
|
||||
(`frm_date_factura`/`frm_date_aviz` -> `frm_facturare_articole(2)` -> `frm_alte_date`) si o data pe
|
||||
formularul unificat, pentru fiecare din **Fara incasare / Chitanta / Bon fiscal / POS-Card**. Se
|
||||
compara `lcListaIncasare` (stringul asamblat inainte de `scrie_factura2`, sectiunea 3.1 al
|
||||
`rute_scriere_antet.md`) si numarul alocat (`poDate.nr_incasare`) — trebuie sa fie identice pe
|
||||
fiecare din cele patru cazuri, nu doar pe bon fiscal.
|
||||
2. **Non-alocare la toggle de pliere, testabila headless** (sectiunea 7, pasul 3, deja formulat ca
|
||||
assert concret pe `poGeneratorNumere`) — pe **doua** scenarii de start, nu unul: document nou
|
||||
(`poDate.incasat=0` la construire) **si** document venit din copiere cu incasare presetata
|
||||
(`poDate.incasat<>0`, sectiunea 4.1 punctul 3) — al doilea caz e cel care azi *deja* aloca la
|
||||
deschidere, exact scenariul pe care criteriul trebuie sa-l acopere explicit.
|
||||
3. **Blocarea grupului C, pe ambele stari ale antetului**: pe un document deja emis, campurile din
|
||||
grupul de incasare raman `Enabled=.F.` atat inainte cat si dupa apasarea lui `but_modifica` — nu
|
||||
doar "la deschidere" (ce ar lasa loc unei regresii daca `but_modifica` le-ar debloca din greseala
|
||||
odata cu restul).
|
||||
4. **Analiticele, pe ambele stari**: pe un document emis, cele patru campuri (venit/cheltuiala, sectie,
|
||||
responsabil, lucrare) afiseaza valoarea curenta din sursa lor (sectiunea 2.3 — de precizat exact
|
||||
care sursa la implementare) si resping orice tentativa de tastare directa sau de deschidere a
|
||||
dialogului de cautare — verificat pe proprietatea `ReadOnly`/`lactiv`, nu doar vizual.
|
||||
5. **Delegat/transport/adresa/text aditional** — paritate camp-cu-camp cu `frm_alte_date` de azi, pe
|
||||
cel putin un document proforma (grup delegat/incasare eliminat, sectiunea 1.4) si unul non-proforma
|
||||
(grup complet), ca sa acopere ambele ramuri ale `Init`-ului de azi (`:3109-3207`).
|
||||
|
||||
*Depinde de:* S3 (but_modifica, mecanismul de blocare a antetului). *Nu depinde de:* etapa II
|
||||
(regenerarea) — grupurile B/C raman blocate prin design in etapa I, nu prin absenta temporara a unei
|
||||
functionalitati care ar trebui testata aici.
|
||||
|
||||
---
|
||||
|
||||
## Ce ramane de decis de Marius
|
||||
|
||||
1. **Textul exact al tooltip-urilor** pentru analiticele read-only (sectiunea 2.2) si pentru grupul de
|
||||
incasare blocat pe document emis (sectiunea 5.3) — doar continutul minim necesar e propus aici, nu
|
||||
formularea finala.
|
||||
2. **Granularitatea butonului/butoanelor de pliere**: un singur comutator pentru toata zona "alte date
|
||||
+ analitice", sau unul separat per subgrup (delegat/transport, incasare, adresa, text, analitice)?
|
||||
Plan sectiunea I nu specifica, iar tiparul gasit (`afiseaza_rulaje`) e per-sectiune unica, nu
|
||||
per-formular-intreg — precedentul nu decide singur granularitatea (sectiunea 6.1).
|
||||
3. **Eager vs. lazy pentru lookup-urile Oracle din `Init`** (delegat/masina ultima factura, casa) —
|
||||
semnalat si in `s3_portare_antet.md` §5 ca discutie deschisa, reconfirmat aici pentru acelasi cod
|
||||
(sectiunea 6.1, ultimul punct).
|
||||
4. **Corectarea gap-ului de dezalocare POS la anulare** (sectiunea 9, punctul 2) — se repara in trecere
|
||||
ca parte a S3b, sau se lasa exact ca azi si se semnaleaza separat ca bug de preluat ulterior?
|
||||
5. **`_checkbox1` vs. `chkDetaliat`** (sectiunea 1.4, sectiunea 9 punctul 3) — care dintre cele doua
|
||||
controale de "listare detaliata" devine campul unic in formularul unificat; ambiguitatea ramane
|
||||
deschisa din S1 si nu s-a inchis aici.
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare incheiata, nu intrerupta la mijloc — toate cele zece sectiuni cerute in briefing sunt
|
||||
complete, cu citate `fisier:linie` verificate direct pe fisierele reale (nu `.bak`). Niciun cod
|
||||
atins, nicio interogare Oracle rulata (nu a fost nevoie — tot ce trebuia era in codul VFP deja citit
|
||||
sau in cele patru rapoarte de referinta). Fisierul e complet la aceasta versiune; nu e nevoie de o
|
||||
sesiune de continuare pentru S3b ca atare. Urmatorul pas natural (nu al acestei sarcini) ar fi S3
|
||||
insusi (blocarea antetului, `but_modifica`) — S3b depinde de el pentru pasii 4 si 6 din sectiunea 7,
|
||||
dar proiectarea de aici nu asteapta acel livrabil, doar implementarea o va astepta.
|
||||
426
docs/cercetare/s3c_sursa_ca_parametru.md
Normal file
426
docs/cercetare/s3c_sursa_ca_parametru.md
Normal file
@@ -0,0 +1,426 @@
|
||||
# S3c — Proiectare: sursa ca parametru, nu ca global
|
||||
|
||||
Cercetare read-only pentru povestea **S3c** din `docs\plan_13_unificare_formular_facturare.md`
|
||||
(`#### S3c`, linia 1855; decizia 12, linia 473; L.3, linia 1733; „Canalul de precompletare”,
|
||||
liniile 465-474). Zero modificari de cod, zero write-back, zero `git_sync.ps1`, zero commit — in
|
||||
niciun produs din `D:\ROA`. Depinde de S2 (`docs\cercetare\s2_factureaza_unificare.md`), a carui
|
||||
proiectare a lasat explicit semnatura lui `factureaza` neschimbata „pentru ca e treaba lui S3c”
|
||||
(sectiunea 5 a acelui raport).
|
||||
|
||||
Status: **complet**.
|
||||
|
||||
---
|
||||
|
||||
## Verdict
|
||||
|
||||
**Se poate face, e o interventie mica pe cod, dar textul planului supraestimeaza cat de „globala"
|
||||
e problema azi.** `goContract` nu e niciodata scris in `ROAFACTURARE` — nici in codul specific
|
||||
produsului, nici in `COMUN`-ul lui — deci ramura de precompletare din contract
|
||||
(`ofacturare_comun.prg:261-297`) e **cod mort in ROAFACTURARE azi**, nu doar teoretic riscant.
|
||||
Asimetria de resetare semnalata la L.3 (`ofundal_facturare.vc2:886-902`) exista textual, dar
|
||||
**nu produce niciun efect observabil azi**, pentru ca nu exista niciun scriitor al lui `goContract`
|
||||
in acest produs care sa lase ceva de resetat. Ea devine un risc real abia daca cineva adauga in
|
||||
viitor, in `ROAFACTURARE`, un cod care scrie `goContract` — situatie in care lipsa resetarii ar
|
||||
deveni activa dintr-o data, tacut. `goComanda`, in schimb, chiar e viu in `ROAFACTURARE`: are doi
|
||||
scriitori (`ocomenzi.vc2:1583` la clic pe „Factureaza”, **si** `ocomenzi.vc2:2199`, ca efect
|
||||
colateral al navigarii in grid), iar reset-ul explicit de la `ofundal_facturare.vc2:900` e singura
|
||||
plasa de siguranta reala azi.
|
||||
|
||||
**Suprafata de regresie e mica si masurata exact**: doi scriitori de convertit
|
||||
(`ocomenzi.vc2:1580-1596` in familia ROAFACTURARE, `ferestre_contracte.vc2:1538-1549`+`:1598-1606`
|
||||
in ROACONTRACTE), trei fisiere comune de atins o singura data fiecare
|
||||
(`ofacturare.prg`, `oproceduri_facturare.prg`, `ofacturare_comun.prg`), si **zero schimbari** la
|
||||
celelalte ~31 puncte de intrare din inventarul S2 — toate cheama `factureaza(N)` cu un singur
|
||||
parametru, deci un al treilea parametru opozitional cu implicit `.F.`/`NULL` nu le atinge.
|
||||
|
||||
**Corectie de scop fata de formularea planului**: „pana se convertesc toti apelantii" nu poate
|
||||
insemna, pentru `goContract`, „pana dispare global-ul" — `goContract` e bufferul de editare al
|
||||
intregului ecran de contracte din ROACONTRACTE (peste 100 de `ControlSource` legate de el,
|
||||
sectiunea 1), populat continuu de navigarea in grid, independent de facturare. Nu va disparea
|
||||
niciodata din ROACONTRACTE la aceasta poveste sau la vreuna viitoare rezonabila — S3c schimba doar
|
||||
**canalul prin care valoarea ajunge la `oDateFactura.Init`**, nu existenta globalei in ROACONTRACTE.
|
||||
Criteriul de „gata” de mai jos (sectiunea 8) e rescris sa reflecte asta.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventarul scrierilor si citirilor `goComanda` / `goContract` in toata suita
|
||||
|
||||
Cautare in `D:\ROA\ROAFACTURARE`, `D:\ROA\ROACONT`, `D:\ROA\ROAGEST`, `D:\ROA\ROAAUTO`,
|
||||
`D:\ROA\ROAACNPRO`, `D:\ROA\ROAIMOB`, `D:\ROA\ROACONTRACTE` — radacina fiecarui produs **si**
|
||||
`COMUN\`-ul lui explicit (capcana „Grep nu vede COMUN”). `D:\ROA\COMUNROA` nu contine niciuna din
|
||||
cele doua variabile (cautare separata, zero rezultate) — confirma ca sursa nu e livrata prin
|
||||
biblioteca partajata, ci prin fisierele COMUN duplicate per produs.
|
||||
|
||||
### `goComanda`
|
||||
|
||||
| Fisier | Linie | Rol | Produse |
|
||||
|---|---|---|---|
|
||||
| `Programe\roafacturare.prg` | `:473-474` | **Declarare**: `PRIVATE goComanda` / `goComanda = null`, la pornirea aplicatiei | doar ROAFACTURARE (fiecare produs are propriul `roafacturare.prg`/echivalent, nu verificat identic — irelevant, doar initializeaza la null) |
|
||||
| `COMUN\clase\ocomenzi.vc2:1580-1596` (`ct_comenzi.do_factura`) | `:1583` | **SCRIE**: `SELECT crsComenzi` / `SCATTER NAME goComanda MEMO`, apoi cheama `facturare_comenzi` | ROAFACTURARE, ROACONT, ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB (fisier identic pe MD5, sectiunea 6) |
|
||||
| `COMUN\clase\ocomenzi.vc2:2191-2207` (`_grdrow1.AfterRowColChange`) | `:2199` | **SCRIE** ca efect colateral: la fiecare schimbare de rand in grid-ul de comenzi, `SCATTER NAME goComanda MEMO`, folosit doar pentru `but_factura1.Visible = (goComanda.facturat = 0)` (`:2202`) — **nu are nicio legatura cu intentia de facturare**, dar suprascrie global-ul de fiecare data cand utilizatorul navigheaza in grid | idem, acelasi fisier identic |
|
||||
| `COMUN\programe\ofacturare_comun.prg:301-328` (`oDateFactura.Init`) | `:301` | **CITESTE**: `If tnTip = 3 And Type('goComanda') = 'O'` -> `.id_client`, `.nume_client`, `.cod_fiscal`, `.listaid`, `.descriere`, `.id_sectie`, plus un `SELECT sectie FROM nom_sectii` pe `goComanda.id_sectie` | identic pe 6/7 produse (sectiunea 6; ROAIMOB diverge cu o linie, dar nu pe acest bloc) |
|
||||
| `Clase\ofundal_facturare.vc2:899-902` (`Page2.Cw3.do_actiune`) | `:900` | **RESETEAZA** explicit: `goComanda = ''` inainte de `DO facturare_comenzi` | doar ROAFACTURARE (fisierul e specific produsului, nu COMUN) |
|
||||
|
||||
Niciun alt fisier, in niciun produs verificat, nu scrie sau citeste `goComanda`.
|
||||
|
||||
### `goContract`
|
||||
|
||||
| Fisier | Linie | Rol | Produse |
|
||||
|---|---|---|---|
|
||||
| `ROACONTRACTE\Programe\roacontracte.prg:559-560` | `:559-560` | **Declarare**: `Public poCtr, goContract` / `Store '' To poCtr, goContract`, la pornirea aplicatiei | doar ROACONTRACTE |
|
||||
| `ROACONTRACTE\Clase\ferestre_contracte.vc2` | ~200 aparitii (liniile 977-11005, tabel complet in sectiunea de cautare) | **SCRIE si CITESTE** ca buffer de editare al intregului formular de contracte: `Scatter Name goContract Memo Blank` la creare (`:1026`, `:1186`), `SCATTER NAME goContract MEMO` la fiecare schimbare de rand in grid (`grid_contracte.AfterRowColChange`, `:1598-1606`), **peste 20** `ControlSource = "goContract.<camp>"` pe controale (combo-uri, textbox-uri), `Gather Name goContract Memo` la salvare (`:1053`, `:2406-2472` etc.) | doar ROACONTRACTE |
|
||||
| `ROACONTRACTE\Clase\ferestre_contracte.vc2:1538-1549` (`but_factura.Click`) | — | **CITESTE implicit** (nu re-scrie): cheama `facturare_contracte` fara sa re-populeze `goContract` — se bazeaza pe scrierea facuta deja de `AfterRowColChange` la selectia randului curent | doar ROACONTRACTE |
|
||||
| `ROACONTRACTE\Clase\outlook2003bar.vc2`, `ofundal_roaclienti.vc2` | multiple | **CITESC** `goContract.id_ctr`/`.id_part` pentru navigare in bara laterala si ecrane de parteneri | doar ROACONTRACTE |
|
||||
| `ROACONTRACTE\Programe\roacontracte.prg`, `oparteneri_contracte.prg`, `oproceduri_roacontracte.prg` | multiple | **CITESC/SCRIU** `goContract` in fluxuri proprii ROACONTRACTE (incasari, plati, rate, garantii) — complet independente de facturare | doar ROACONTRACTE |
|
||||
| `COMUN\programe\ofacturare_comun.prg:261-297` (`oDateFactura.Init`) | `:261` | **CITESTE**: `If INLIST(m.tnTip, 2, 6, 52) And Type('goContract') <> 'U'` -> `.id_client`, `.nume_client`, `.cod_fiscal`, `.listaid`, `.descriere`, `.id_sectie`, `.sectie`, `.id_responsabil`, `.responsabil`, `.id_valuta`, `.nume_valuta`, plus interogare `fact_vcontracte` pentru scadenta | identic pe 6/7 produse (ROAIMOB diverge cu exact aceasta linie lipsa — sectiunea 6) |
|
||||
| `COMUN\clase\ferestre_atasamente.vc2`, `COMUN\programe\oproceduri_atasamente.prg` | 5 aparitii | **CITESC** `goContract.id_ctr`, dar **numai** in ramura `Case Upper(Alltrim(gcNumeProgram)) = "ROACONTRACTE"` — cod prezent in fiecare copie COMUN (deci si in ROAFACTURARE), dar mort acolo pentru ca `gcNumeProgram` nu e niciodata `"ROACONTRACTE"` in afara procesului ROACONTRACTE | identic pe toate produsele verificate, dar activ doar in ROACONTRACTE |
|
||||
|
||||
**Confirmare directa: `goContract` nu e scris niciunde in `ROAFACTURARE`** — nici in `Programe\`,
|
||||
`Clase\`, `Ferestre\`, `Meniuri\`, nici in `COMUN\` (cautare pe `PUBLIC goContract`/`Public
|
||||
goContract` in tot arborele: zero rezultate, tabelul complet cu toate declaratiile `PUBLIC go*`
|
||||
gasite e in sectiunea 2). Singura mentiune a lui `goContract` in ROAFACTURARE e citirea garda de
|
||||
`Type()` din `ofacturare_comun.prg:261`, care ramane mereu falsa (`Type('goContract') = 'U'`) intr-o
|
||||
sesiune ROAFACTURARE.
|
||||
|
||||
## 2. Verdict pe L.3 — asimetria de resetare
|
||||
|
||||
**Textul din cod e adevarat, dar efectul practic e altul decat sugereaza formularea „de verificat”
|
||||
din plan.**
|
||||
|
||||
- **Asimetria exista, literal**: `ofundal_facturare.vc2:899-902` (`Page2.Cw3`, ruta genericaa de
|
||||
comanda) face `goComanda = ''` inainte de `DO facturare_comenzi`; `ofundal_facturare.vc2:886-897`
|
||||
(`Page2.Cw2`, ruta generica de contract) **nu** face echivalentul pentru `goContract` — cheama
|
||||
direct `DO facturare_contracte WITH 'FACTURA LEI'/'INVOICE'/'FACTURA VALUTA'`.
|
||||
- **Dar `facturare_contracte` (`oproceduri_facturare.prg:119-136`) nu citeste si nu scrie
|
||||
`goContract` deloc** — primeste un string de tip (`'FACTURA LEI'` etc.) si cheama direct
|
||||
`factureaza(2)`/`factureaza(6)`/`factureaza(52)`. Precompletarea din contract se intampla
|
||||
**exclusiv** in `oDateFactura.Init`, la citirea globalei — nu exista niciun pas intermediar care
|
||||
ar putea fi „resetat”.
|
||||
In ROAFACTURARE, calea genericaa (Cw2) las utilizatorul sa aleaga contractul **in formular**,
|
||||
printr-un combo populat din `crscontracte` legat de `poDate.listaid`
|
||||
(`ofacturare.prg:283-291,433-440`) — un canal complet diferit, care nu trece prin `goContract`
|
||||
deloc (confirmat citind codul, sectiunea 3).
|
||||
- **Deci, in ROAFACTURARE azi, nu exista nicio secventa executabila in care `goContract` sa fie
|
||||
citit cu o valoare veche.** Pentru ca asta sa se intample ar trebui ca (a) ceva sa fi scris
|
||||
`goContract` mai devreme in aceeasi sesiune ROAFACTURARE — **nu exista niciun asemenea cod azi** —
|
||||
si (b) urmatorul apel sa fie cu `tnTip` in `(2, 6, 52)`. Fara (a), (b) singur nu ajunge nicaieri.
|
||||
- **Verdictul pe L.3**: asimetria de resetare **e reala ca defect de simetrie in cod** (o ruta isi
|
||||
curata globala inainte de apel, cealalta nu), dar **inert azi in ROAFACTURARE** — nu exista niciun
|
||||
document gresit care se poate emite azi din cauza ei, pentru ca variabila pe care ar trebui sa o
|
||||
resetezi nu e niciodata populata in acest produs. Devine un risc real **doar** daca un cod viitor
|
||||
(in ROAFACTURARE) incepe sa scrie `goContract` fara sa adauge simetric si resetul — exact genul de
|
||||
capcana pe care „parametru explicit, fara global implicit” o elimina structural, nu prin inca o
|
||||
linie de reset de tinut minte.
|
||||
- **In ROACONTRACTE, unde `goContract` chiar e viu, nu exista o ruta „generica fara precompletare”
|
||||
analoaga lui Cw2** — `but_factura.Click` (`:1538-1549`) e singurul punct de intrare si presupune
|
||||
intotdeauna un contract selectat in grid (populat de `AfterRowColChange`, `:1598-1606`, la fiecare
|
||||
schimbare de rand). Riscul teoretic acolo nu e „global ramas din alta sesiune de facturare”, ci
|
||||
„utilizatorul apasa Factureaza fara sa fi selectat explicit un rand dupa un refresh programatic” —
|
||||
un caz marginal, netratat de L.3 si nelegat de asimetria semnalata in plan.
|
||||
|
||||
## 3. Doar doua globale, sau mai sunt?
|
||||
|
||||
**Doar doua.** Verificat prin citirea completa a `oDateFactura.Init`
|
||||
(`ofacturare_comun.prg:223-332`) si a rutarii cursoarelor de articole din `ofacturare.prg:260-308`:
|
||||
|
||||
- **`goDate`, `gnIdSet` ca surse de precompletare — nu exista.** `goDate` nu apare niciunde in
|
||||
`ofacturare.prg`/`ofacturare_comun.prg` (cautare directa, zero rezultate). `gnIdSet` nu exista ca
|
||||
atare — parametrul se numeste `tnIdSet`/`lnIdSet`, calculat **local** in `factureaza`
|
||||
(`lnIdSet = 25000 + tnTip - 1 + gnScadereStoc * 10`, `ofacturare.prg:123`) din `tnTip` si un
|
||||
optiune de configurare (`gnScadereStoc`), nu un canal de sursa.
|
||||
- **Singurele doua verificari `Type('go...')` din `oDateFactura.Init` sunt exact `goContract`
|
||||
(`:261`) si `goComanda` (`:301`)** — cautare `Type\('go` pe tot `ofacturare.prg` +
|
||||
`ofacturare_comun.prg`: zero alte rezultate in afara comutatorului de dezvoltator
|
||||
`gnFacturareNou` (irelevant aici, documentat in S2).
|
||||
- **`poDate.listaid` pentru avize (tip 4, 21, 28, 42, 47) NU trece printr-un global** — se scrie
|
||||
direct din cursorul de selectie al formularului: `poDate.listaid = Iif(Inlist(poDate.tip, 3, 21,
|
||||
28, 42, 47), Alltrim(Str(id_comanda)), [])` (`ofacturare_comun.vc2:4255`, si varianta similara la
|
||||
`:4062`, `:7271`) — populat dintr-un `Scan`/`Locate` peste cursorul cu avizele bifate de utilizator
|
||||
in acelasi apel, nu dintr-o variabila globala persistenta intre apeluri. **Avizele nu intra in
|
||||
S3c** — nu au canal implicit de eliminat.
|
||||
- **Copierea (`toFactura`) e deja parametru, nu global** — `copiere_factura(toFactura)`
|
||||
(`oproceduri_facturare.prg:150-153`) -> `factureaza(toFactura.Tip, toFactura)`, si in
|
||||
`oDateFactura.Init`/logica de copiere valorile vin din `toDateAnterior` (parametrul), de exemplu
|
||||
`.listaid = toDateAnterior.id_vanzare` (`ofacturare_comun.vc2:387`) — **e exact precedentul pe
|
||||
care S3c il extinde la comanda/contract**, nu un al treilea canal implicit de adaugat la lista.
|
||||
- **`poDate` insusi nu e un canal de scurgere intre apeluri** — se creeaza cu `Createobject` la
|
||||
fiecare intrare in bucla lui `factureaza` (`ofacturare.prg:184`, doar `If Type('poDate') <> 'O'`,
|
||||
ceea ce e adevarat prima data si ramane fals doar in interiorul aceluiasi apel, la reintrarile
|
||||
bucla `Do While lnRaspuns = 6`) — deci nu poate purta stare intre doua clicuri distincte pe
|
||||
„Factureaza”.
|
||||
|
||||
**Concluzie**: `goComanda` si `goContract` sunt singurele doua canale implicite relevante pentru
|
||||
decizia 12. Nu mai exista o a treia variabila de convertit odata cu ele.
|
||||
|
||||
## 4. Semnatura propusa
|
||||
|
||||
**`factureaza`** (`COMUN\programe\ofacturare.prg:81-82`), azi `Lparameters tnTip, toFactura`:
|
||||
|
||||
```
|
||||
Lparameters tnTip, toFactura, toSursa
|
||||
```
|
||||
|
||||
- **`toSursa`** — obiect, implicit `NULL` (nepasat de niciun apelant existent). Poarta **fie** un
|
||||
obiect cu forma lui `goComanda` (cand `tnTip = 3`), **fie** un obiect cu forma lui `goContract`
|
||||
(cand `tnTip IN (2, 6, 52)`) — exact aceeasi dualitate pe care codul de azi o rezolva deja prin
|
||||
ramificare pe `tnTip` in `oDateFactura.Init` (`:261` vs `:301`), deci nu introduce un tip nou de
|
||||
decizie, doar muta sursa valorii.
|
||||
- **Pozitia**: al treilea parametru, dupa `toFactura`, niciodata inaintea lui — la fel ca precedentul
|
||||
`V_TAXCODE`/`V_LOT` citat de tine: apelurile VFP sunt pozitionale, iar singurul mod sa nu rupi
|
||||
apelantii existenti e sa adaugi la coada, cu implicit. Toate cele ~31 de apeluri din inventarul S2
|
||||
care pasesc doar `tnTip` raman **neschimbate** — VFP completeaza automat parametrii nepasati la
|
||||
coada cu `.F.` (verificat comportamental: `Type()` pe un parametru nepasat intoarce `'L'` cu
|
||||
valoarea `.F.`, nu `'U'` — de tratat explicit in garda, vezi sectiunea 5).
|
||||
- **De ce nu inlocuieste `toFactura`**: `toFactura` inseamna „copiaza factura asta” (un document deja
|
||||
emis), `toSursa` inseamna „precompleteaza din documentul asta” (o comanda sau un contract inca
|
||||
nefacturat) — semantic distincte, si `copiere_factura` (`oproceduri_facturare.prg:150-153`) ar
|
||||
putea teoretic avea nevoie de amandoua simultan in viitor (copiere + realocare pe alt contract) —
|
||||
motiv suplimentar sa nu le contopesti intr-un singur parametru.
|
||||
|
||||
**Constructorul `oDateFactura.Init`** (`ofacturare_comun.prg:223-224`), azi
|
||||
`Lparameters tnIdSet, tnTip`:
|
||||
|
||||
```
|
||||
Lparameters tnIdSet, tnTip, toSursa
|
||||
```
|
||||
|
||||
- Aceeasi regula de pozitionare: la coada. Singurul apelant azi e
|
||||
`Createobject("oDateFactura", lnIdSet, tnTip)` din `factureaza` (`ofacturare.prg:184`) — devine
|
||||
`Createobject("oDateFactura", lnIdSet, tnTip, toSursa)`. Nu exista alt loc din cod care
|
||||
instantiaza `oDateFactura` (cautare `Createobject("oDateFactura"` pe tot `COMUN`: un singur
|
||||
rezultat).
|
||||
|
||||
## 5. Mecanismul de sursa de rezerva
|
||||
|
||||
**Regula**: la fiecare din cele doua ramuri, se prefera `toSursa` daca a fost pasat ca obiect; daca
|
||||
nu, se cade pe globala, exact ca azi. Nu se schimba forma datelor citite (aceleasi campuri), doar
|
||||
sursa lor.
|
||||
|
||||
```foxpro
|
||||
* ofacturare_comun.prg, oDateFactura.Init — inlocuieste liniile 261 si 301
|
||||
|
||||
Local loSursa
|
||||
loSursa = Iif(Type('toSursa') = 'O', toSursa, Null)
|
||||
|
||||
If INLIST(m.tnTip, 2, 6, 52) And (Type('loSursa') = 'O' Or Type('goContract') <> 'U')
|
||||
If Type('loSursa') <> 'O'
|
||||
loSursa = goContract
|
||||
Endif
|
||||
.id_client = loSursa.id_part
|
||||
.nume_client = loSursa.denumire
|
||||
* ... restul neschimbat, doar goContract -> loSursa
|
||||
Endif
|
||||
|
||||
loSursa = Iif(Type('toSursa') = 'O', toSursa, Null) && re-evaluat, nu se refoloseste variabila de mai sus intre ramuri
|
||||
|
||||
If tnTip = 3 And (Type('loSursa') = 'O' Or Type('goComanda') = 'O')
|
||||
If Type('loSursa') <> 'O'
|
||||
loSursa = goComanda
|
||||
Endif
|
||||
.id_client = loSursa.id_part
|
||||
* ... restul neschimbat, doar goComanda -> loSursa
|
||||
Endif
|
||||
```
|
||||
|
||||
De ce doua evaluari separate ale lui `loSursa` (nu una singura la inceput): `toSursa` poarta o
|
||||
singura forma per apel (fie comanda, fie contract, niciodata amandoua — `tnTip` decide exclusiv
|
||||
care), dar garda trebuie sa verifice `tnTip` inainte sa presupuna forma, la fel ca azi. O variabila
|
||||
locala unica evita sa scrii `toSursa` de doua ori in tot blocul, fara sa schimbe logica.
|
||||
|
||||
**Cum se stie cand se poate scoate globala** — criteriu verificabil, nu „cand se convertesc toti”:
|
||||
|
||||
- **Pentru `goComanda`**: nu se poate scoate niciodata complet din `ROAFACTURARE`, pentru ca al
|
||||
doilea ei scriitor (`ocomenzi.vc2:2191-2207`, `AfterRowColChange`) nu are nicio legatura cu
|
||||
`factureaza` — exista doar pentru `but_factura1.Visible`. Criteriul realist: **linia de citire in
|
||||
`oDateFactura.Init` (`:301`) poate deveni neconditionata de fallback abia cand se confirma, prin
|
||||
`git_sync.ps1` + Grep, ca niciun apel la `factureaza(3, ...)` mai lasa `toSursa` nepasat** — adica
|
||||
se verifica apelantii lui `factureaza`, nu scriitorii globalei (care raman, pentru alt scop).
|
||||
- **Pentru `goContract`**: acelasi lucru, dar cu o observatie in plus — global-ul insusi
|
||||
(`Public goContract` in `roacontracte.prg:559-560`) **nu va disparea niciodata** cat timp exista
|
||||
formularul de contracte din ROACONTRACTE, care il foloseste ca buffer de editare, nu doar ca
|
||||
transport spre facturare. Criteriul de scos fallback-ul: **cand toate apelurile catre
|
||||
`factureaza(2/6/52, ...)`, in toate produsele, pasesc `toSursa` explicit** — verificabil cu
|
||||
aceeasi comanda `Select-String` ca in S2, aplicata pe `factureaza(2` / `factureaza(6` /
|
||||
`factureaza(52` in loc de `factureaza2`.
|
||||
- Pana atunci, fallback-ul ramane — el nu costa nimic in productie (o verificare `Type()` in plus),
|
||||
si e singura plasa de siguranta pentru cele ~31 puncte de intrare care nu au fost, nu vor fi, si
|
||||
nu trebuie sa fie convertite (nu au sursa de precompletat).
|
||||
|
||||
## 6. Cele N copii ale fisierelor atinse — identice sau divergente?
|
||||
|
||||
MD5 pe fiecare fisier, in cele 7 produse care au copie (`ROAFACTURARE`, `ROACONT`, `ROAGEST`,
|
||||
`ROAAUTO`, `ROAACNPRO`, `ROAIMOB`, `ROACONTRACTE`):
|
||||
|
||||
| Fisier | Rezultat |
|
||||
|---|---|
|
||||
| `COMUN\programe\ofacturare.prg` (2662 linii) | **Identic pe toate cele 7** — un singur MD5 |
|
||||
| `COMUN\programe\oproceduri_facturare.prg` (2393 linii) | **Identic pe toate cele 7** — un singur MD5 |
|
||||
| `COMUN\clase\ocomenzi.vc2` (8301 linii) | **Identic pe cele 6** care il au (nu verificat separat in ROACONTRACTE, care nu are comenzi) |
|
||||
| `COMUN\programe\ofacturare_comun.prg` (2224 linii) | **Identic pe 6 din 7** — `ROAIMOB` diverge cu **exact o linie lipsa**: `:264`, `.cod_fiscal = ALLTRIM(NVL(goContract.cod_fiscal, ''))`, absenta din copia ROAIMOB (2223 linii). Restul fisierului, inclusiv blocul `goComanda`/`goContract` de la `:261-328`, e identic caracter cu caracter. |
|
||||
|
||||
**Spre deosebire de `ofacturare.vc2` (7 copii, 4 variante MD5 distincte, citat ca precedent de
|
||||
comparat)**, fisierele atinse de S3c sunt aproape perfect sincronizate — un singur punct de drift,
|
||||
minor si izolat. Concluzie pentru propagare: modificarea din `ofacturare.prg` si
|
||||
`oproceduri_facturare.prg` se poate copia **caracter cu caracter** in toate cele 7 produse fara nicio
|
||||
adaptare. Modificarea din `ofacturare_comun.prg` se poate copia identic in 6 produse, dar **in
|
||||
ROAIMOB trebuie aplicata pe baza divergentei existente** (linia `cod_fiscal` lipseste deja acolo —
|
||||
o copiere oarba a diff-ului ar putea sa nu se aplice curat sau sa reintroduca acea linie fara sa fie
|
||||
intentionat; de verificat manual la sincronizare, nu doar copiat).
|
||||
|
||||
`ferestre_contracte.vc2` (ROACONTRACTE) e specific produsului, nu are copii de sincronizat.
|
||||
|
||||
## 7. Suprafata de regresie masurata
|
||||
|
||||
**Fisiere care chiar se modifica** (5, plus 1 verificare de sincronizare):
|
||||
|
||||
| # | Fisier | Ce se schimba | Cate copii de propagat |
|
||||
|---|---|---|---|
|
||||
| 1 | `COMUN\programe\ofacturare.prg:81-82` | semnatura `factureaza`, +`toSursa` | 7 (identice, copiere directa) |
|
||||
| 2 | `COMUN\programe\ofacturare_comun.prg:223-224, 261-328` | semnatura `Init`, +`toSursa`; ramurile de citire cu fallback | 7 (6 identice + ROAIMOB cu drift de reconciliat) |
|
||||
| 3 | `COMUN\programe\oproceduri_facturare.prg:119-141` | `facturare_contracte(tcTip, toSursa)`, `facturare_comenzi(toSursa)`, relay catre `factureaza(N, NULL, toSursa)` | 7 (identice, copiere directa) |
|
||||
| 4 | `COMUN\clase\ocomenzi.vc2:1580-1596` (`do_factura`) | `SCATTER NAME loComanda MEMO` (local, nu global) + `DO facturare_comenzi WITH loComanda IN oproceduri_facturare.prg` | 6 (identice, copiere directa) |
|
||||
| 5 | `ROACONTRACTE\Clase\ferestre_contracte.vc2:1538-1549` (`but_factura.Click`) | trimite `goContract` (sau o copie locala scatter-uita din randul curent) explicit ca al doilea parametru la `DO facturare_contracte WITH lcTip, goContract IN ...` | 1 (specific ROACONTRACTE) |
|
||||
|
||||
**Apelanti care NU se modifica** (confirmat, nu presupus): toate cele ~31 de puncte de intrare din
|
||||
inventarul S2 (`Meniuri\politica.mn2`, `Meniuri\contracte.mn2`, `Meniuri\aviz_*.mn2`,
|
||||
`ofundal_facturare.vc2:883-905`) cheama `factureaza(N)` cu un singur argument sau
|
||||
`facturare_contracte`/`facturare_comenzi` fara sursa — al treilea parametru ramane implicit `NULL`,
|
||||
fallback-ul pe global preia (pentru cele care oricum nu aveau sursa reala, fallback-ul da acelasi
|
||||
rezultat ca azi: nimic de precompletat). **Inclusiv `ofundal_facturare.vc2:886-902` (Page2.Cw2/Cw3)
|
||||
raman neschimbate** — `Cw3` isi pastreaza `goComanda = ''` (devine redundant, dar inofensiv, odata ce
|
||||
`do_factura` trimite explicit; se poate curata separat, nu la aceasta poveste), `Cw2` ramane cum e.
|
||||
|
||||
**Total: 5 fisiere reale de editat, in 4 continuturi distincte de modificare** (fisierele #1-#3 au
|
||||
acelasi continut in toate copiile lor), propagate in pana la 7 produse — mult sub suprafata unei
|
||||
modificari „de suita” tipice, pentru ca `ofacturare.prg`/`oproceduri_facturare.prg` sunt deja
|
||||
perfect sincronizate (sectiunea 6).
|
||||
|
||||
## 8. Ordinea de executie, ca suita sa nu fie stricata niciun moment
|
||||
|
||||
Spre deosebire de S2 (unde `factureaza2` nu avea alt apelant decat el insusi, deci risc de stricare
|
||||
aproape nul), aici **exista o fereastra in care semnaturile trebuie sa fie compatibile intre fisiere
|
||||
diferite** (`ofacturare.prg` cheama `oDateFactura::Init`, `oproceduri_facturare.prg` cheama
|
||||
`factureaza`) — ordinea trebuie sa garanteze ca niciun punct intermediar nu are un apelant cu
|
||||
semnatura veche impotriva unui apelat cu semnatura noua incompatibila. Pentru ca **toti parametrii
|
||||
noi sunt optionali, la coada**, riscul e de fapt mic — o semnatura veche care cheama o rutina noua
|
||||
functioneaza (parametrul lipsa devine implicit), problema ar aparea doar invers (rutina veche
|
||||
chemata cu un parametru in plus, care s-ar ignora silentios in VFP — tot fara eroare, dar fara
|
||||
efect). Deci ordinea de mai jos e despre corectitudine, nu despre a evita crash-uri:
|
||||
|
||||
1. **`oDateFactura.Init`** (`ofacturare_comun.prg`) — adauga `toSursa`, muta logica de citire pe
|
||||
modelul din sectiunea 5. Se poate face si testa izolat: fara niciun apelant care sa paseze
|
||||
`toSursa` inca, comportamentul ramane identic cu azi (fallback pe global, mereu).
|
||||
2. **`factureaza`** (`ofacturare.prg`) — adauga `toSursa`, il paseaza la `Createobject("oDateFactura",
|
||||
lnIdSet, tnTip, toSursa)`. Inca niciun apelant nu paseaza `toSursa` — comportament neschimbat.
|
||||
3. **`oproceduri_facturare.prg`** — `facturare_contracte`/`facturare_comenzi` primesc `toSursa` si il
|
||||
relaeaza. Inca niciun apelant real nu il paseaza — comportament neschimbat.
|
||||
4. **Abia acum, apelantii reali**: `ocomenzi.vc2:do_factura` trimite `loComanda` explicit;
|
||||
`ferestre_contracte.vc2:but_factura.Click` trimite `goContract` explicit (in ROACONTRACTE). Din
|
||||
acest punct, comportamentul chiar se schimba (sursa vine din parametru, nu din citirea globalei),
|
||||
dar rezultatul trebuie sa fie identic — parametrul poarta exact aceleasi campuri pe care globala
|
||||
le avea.
|
||||
5. **Sincronizare in celelalte 6 produse**: pasii 1-3 se copiaza caracter cu caracter (identice,
|
||||
sectiunea 6), cu atentia speciala la ROAIMOB (drift de o linie). Pasul 4 pe partea de comenzi se
|
||||
copiaza si el (fisierul e identic in toate). Nu exista pas 4 de propagat pe partea de contract in
|
||||
afara de ROACONTRACTE (nu exista alt produs cu formular de contracte care sa scrie `goContract`).
|
||||
6. **Un singur produs se poate testa complet integrat de la sine**: ROAFACTURARE (pasii 1-4, partea
|
||||
de comanda). ROACONTRACTE cere o rulare separata (build propriu, sincronizat manual) pentru
|
||||
pasul 5 pe partea de contract — acelasi tip de limitare semnalata deja in S2 pentru
|
||||
`factureaza2`.
|
||||
|
||||
## 9. Ce nu se poate testa headless
|
||||
|
||||
- **Toate cele ~31+2 puncte de intrare sunt declansate din UI** (clic pe buton, `ON SELECTION BAR`
|
||||
in meniu) — niciunul nu are un test headless existent, la fel ca in S2.
|
||||
- **Testarea „parametrul castiga peste global” cere manipulare de sesiune**: singurul mod sa verifici
|
||||
ca `toSursa` are prioritate e sa populezi manual `goComanda`/`goContract` cu o valoare **diferita**
|
||||
de cea din parametru si sa confirmi ca documentul rezultat foloseste parametrul — asta cere fie
|
||||
cod de test care seteaza global-ul inainte de apel (posibil headless, dar artificial fata de
|
||||
fluxul real), fie o sesiune interactiva VFP cu comenzi tastate manual in fereastra de comenzi.
|
||||
- **Efectul colateral al `AfterRowColChange` (`ocomenzi.vc2:2199`) nu se poate simula headless** —
|
||||
harnessul `-A -T` nu declanseaza fiabil evenimente de grid (confirmat de precedentul din memorie
|
||||
„Coloanele de grid nu se materializeaza headless”), deci nu se poate verifica automat ca navigarea
|
||||
in grid tot suprascrie `goComanda` dupa modificare (comportament care ramane neschimbat, dar
|
||||
trebuie confirmat vizual, nu presupus).
|
||||
- **ROACONTRACTE nu poate fi testat din arborele de lucru ROAFACTURARE** — cere build si rulare
|
||||
separata, cu propriul executabil si propria sincronizare a `ofacturare.prg`/`ofacturare_comun.prg`
|
||||
/`oproceduri_facturare.prg` (nu sunt acelasi fisier fizic, sunt copii).
|
||||
- **Rezultatul final** (facturare emisa cu antetul corect precompletat din comanda/contract) se
|
||||
verifica doar prin continutul lui `crsfactura`/`VANZARI`/formularul de antet, vizual sau prin
|
||||
interogare Oracle read-only dupa emitere — nu exista assert automat de facut.
|
||||
|
||||
## 10. Riscuri si ce ramane de decis de Marius
|
||||
|
||||
**Riscuri**:
|
||||
|
||||
- **Cel mai probabil sa se strice**: garda `Type('loSursa') = 'O' Or Type('goContract') <> 'U'` din
|
||||
sectiunea 5 — daca se scrie gresit (de exemplu `And` in loc de `Or`), fallback-ul pe global s-ar
|
||||
rupe silentios pentru toti apelantii care inca nu au fost convertiti, fara nicio eroare vizibila
|
||||
(`oDateFactura` ar porni pur si simplu fara precompletare). Se testeaza explicit pe cel putin un
|
||||
apel neconvertit (orice `factureaza(N)` cu un singur argument, tip 1) dupa modificare.
|
||||
- **Al doilea cel mai probabil**: parametrul nou nepasat trebuie verificat `Type('toSursa') = 'O'`,
|
||||
nu `Type('toSursa') <> 'U'` — un parametru VFP nepasat nu e `'U'` (asta e pentru variabile
|
||||
nedeclarate), ci `'L'` cu valoarea `.F.` implicita a lui `LPARAMETERS`. O garda gresita
|
||||
(`<> 'U'`) ar trece mereu adevarat si ar incerca sa citeasca `.id_part` de pe `.F.`, eroare de
|
||||
rulare imediata la primul apel neconvertit. **De verificat cu un test minimal inainte de a atinge
|
||||
fisierele reale** — nu presupune, verifica comportamentul `Type()` pe un parametru nepasat intr-un
|
||||
`.prg` de proba.
|
||||
- **ROACONTRACTE, singurul apelant real din alt produs** — acelasi risc semnalat in S2: netestabil
|
||||
din acest arbore de lucru, cere confirmare manuala separata.
|
||||
- **Drift-ul de o linie din `ofacturare_comun.prg` in ROAIMOB** — o copiere mecanica a diff-ului ar
|
||||
putea esua silentios sau reintroduce linia lipsa fara sa fie intentia — de aplicat manual acolo,
|
||||
nu prin copy-paste orb.
|
||||
- **`Cw3.do_actiune` (`ofundal_facturare.vc2:900`) ramane cu `goComanda = ''` redundant** dupa
|
||||
conversia lui `do_factura` — nu e o problema (ramane inofensiv), dar merita mentionat explicit ca
|
||||
„lasat asa, intentionat” in codul livrat, ca sa nu para o omisiune la revizuirea diff-ului.
|
||||
|
||||
**Ce ramane de decis de Marius**:
|
||||
|
||||
1. **Forma lui `toSursa`**: ramane duck-typing pe obiect scatter (ca azi), sau se formalizeaza o
|
||||
clasa cu doua forme (`oSursaComanda`/`oSursaContract`)? Recomandare: ramane duck-typing — nu
|
||||
schimba nimic functional, adauga doar o clasa noua de intretinut pentru un beneficiu marginal la
|
||||
aceasta poveste.
|
||||
2. **Se face si conversia partii de comanda si cea de contract in acelasi commit, sau separat?**
|
||||
Ambele ating exact aceleasi trei fisiere comune (`ofacturare.prg`, `ofacturare_comun.prg`,
|
||||
`oproceduri_facturare.prg`) — separarea nu reduce suprafata de regresie pe fisierele comune, doar
|
||||
amana testarea reala pe ROACONTRACTE. Recomandare: **un singur commit**, cu testare manuala pe
|
||||
ambele produse inainte de a-l considera gata.
|
||||
3. **Se curata acum redundanta `goComanda = ''` de la `Cw3` (sectiunea „Riscuri”), sau se lasa pentru
|
||||
o poveste ulterioara de curatenie?** Nu afecteaza corectitudinea — decizie de stil, nu de
|
||||
comportament.
|
||||
4. **Criteriul de „gata” rescris** (inlocuieste formularea din plan, linia 1861-1862):
|
||||
- *Structural*: `Select-String -Path ofacturare.prg,ofacturare_comun.prg,oproceduri_facturare.prg
|
||||
-Pattern 'toSursa'` gaseste parametrul in toate cele trei fisiere, in toate cele 7 produse care
|
||||
au copii, cu semnatura identica.
|
||||
- *Comportamental, cale convertita*: facturarea pornita din `ocomenzi.vc2:do_factura` cu
|
||||
`goComanda` populat manual cu **alta** comanda decat cea trimisa prin `toSursa` produce
|
||||
documentul corespunzator parametrului, nu globalei — testat manual, cu global-ul deliberat
|
||||
„murdar” dintr-o navigare anterioara in grid.
|
||||
- *Comportamental, cale neconvertita*: oricare din cele ~31 de apeluri care trimit doar `tnTip`
|
||||
produce acelasi document ca inainte de modificare (fallback pe global, sau fara precompletare
|
||||
acolo unde nu exista global de citit).
|
||||
- *ROACONTRACTE*: facturarea din `but_factura.Click` cu contractul trimis explicit prin
|
||||
`toSursa` produce acelasi rezultat ca azi (precompletare identica), verificat manual pe build
|
||||
separat.
|
||||
|
||||
---
|
||||
|
||||
## Ce nu s-a putut stabili si de ce
|
||||
|
||||
- **Continutul exact al obiectului `loComanda`/`goContract` scaturat** nu a fost comparat camp cu
|
||||
camp intre ce citeste azi `oDateFactura.Init` si ce ar contine un `SCATTER` facut in afara
|
||||
contextului `crsComenzi`/`cContracte` curent — presupunerea (rezonabila, dar neverificata pe date)
|
||||
e ca structura cursorului nu se schimba intre cele doua puncte de scatter.
|
||||
- **Daca exista alte produse din suita (dincolo de cele 7 verificate) care au propriile copii ale
|
||||
`ofacturare.prg`/`ofacturare_comun.prg`/`oproceduri_facturare.prg`** — cautarea s-a limitat la
|
||||
produsele numite explicit in sarcina; alte ~30 de produse mentionate in S2 ca avand copii ale
|
||||
`ofacturare.prg` (ROARETAIL etc.) nu au fost verificate pentru `goComanda`/`goContract` la aceasta
|
||||
poveste.
|
||||
- **Testarea reala pe ROACONTRACTE** nu a fost efectuata (read-only, fara rulare de cod) — doar
|
||||
confirmata structura codului sursa.
|
||||
|
||||
## Bug-uri semnalate, nereparate
|
||||
|
||||
Niciunul nou — cercetarea a confirmat un defect de simetrie deja semnalat in plan (L.3), l-a
|
||||
verificat pe cod pana la verdictul „inert azi in ROAFACTURARE” (sectiunea 2), si nu a gasit alte
|
||||
probleme in fisierele atinse.
|
||||
482
docs/cercetare/s4_cautare_articole_server.md
Normal file
482
docs/cercetare/s4_cautare_articole_server.md
Normal file
@@ -0,0 +1,482 @@
|
||||
# Cercetare — proiectare S4: cautarea articolelor pe server, in linie
|
||||
|
||||
Investigatie READ-ONLY pentru povestea **S4** din `docs\plan_13_unificare_formular_facturare.md:1865-1874`,
|
||||
plus sectiunea `### C. Incarcarea articolelor — de ce dureaza si ce inlocuieste` (`:589-614`).
|
||||
Fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit. `COMUN\clase\ofacturare_comun.vc2`
|
||||
si `COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar citite, nu atinse. Pe Oracle doar
|
||||
`SELECT`, prin exportul `PACK_FACTURARE` de pe disc — nu s-a rulat nimic pe server.
|
||||
|
||||
## Verdict (rezumat)
|
||||
|
||||
Mecanismul de inlocuire propus in plan **exista deja, dar e un prototip la jumatate**:
|
||||
`grd_factura.cCodMat.cboCodmat` / `cCboDenumire` (`ofacturare.vc2:16751-16797`, wiring la
|
||||
`:19288-19334`) cauta pe server prin `combosql` si scriu `codmat`/`denumire`/`id_articol` in
|
||||
`crsfactura` — dar **nu scriu nimic altceva**, pentru ca sursa lor (`vnom_articole`) nu are pret,
|
||||
TVA, valuta, `id_pol`, `gestionabil`. Asta confirma exact ce zice planul la punctul C: partea Oracle
|
||||
de facut e o **varianta filtrata pe articol a celor cinci cursoare de facturare**, nu o cautare noua.
|
||||
|
||||
Descoperirea care schimba proiectarea fata de textul din plan: **`crsarticole` nu e doar sursa de
|
||||
populare a gridului — e un registru al cantitatii ramase de facturat**, citit si scris de
|
||||
`do_adauga_tot`, `do_sterge` si `do_scrie_factura` pentru toate tipurile cu document sursa (comanda,
|
||||
aviz, contract-lista-de-preturi). Stergerea unei linii **reface** cantitatea in `crsarticole`
|
||||
(`:14640-14669`), iar la scriere se face `Calculate Sum(cantitate) To lnCantitateRamasa` peste
|
||||
`crsarticole` ca sa se decida daca se inchide automat comanda/avizul (`:14303-14311`, `:14334-14338`).
|
||||
Asta inseamna ca **incarcarea in masa nu poate disparea pentru aceste tipuri**, indiferent de S4 —
|
||||
nu doar pentru ca planul a decis sa pastreze "adauga tot", ci pentru ca bookkeeping-ul de cantitate
|
||||
ramasa e cablat direct pe cursorul incarcat. S4 se aplica deci curat doar pe ramurile de **lista de
|
||||
preturi** (`cursor_preturi`, plus jumatate din `cursor_contract` si `cursor_gestiune`) — vezi punctul 7.
|
||||
|
||||
A doua descoperire: calea de scriere a pretului la nivel de linie (`adauga_articol_factura`,
|
||||
verificata separat in S10, `docs\cercetare\s10_pret_rederivat.md`) **primeste deja pretul ca
|
||||
parametru** in loc sa-l re-deriveze — exact precedentul pe care trebuie sa-l urmeze si varianta
|
||||
filtrata: cauta pretul o singura data, la alegerea liniei, si il transmite mai departe neschimbat.
|
||||
|
||||
## 1. Inventarul celor cinci cursoare Oracle
|
||||
|
||||
Sursa: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (corpul
|
||||
pachetului; liniile de mai jos sunt pe corp, `:2138+`, nu pe spec `:335+` — offset **+17** fata de
|
||||
citatele vechi din plan, confirmat si in S10).
|
||||
|
||||
### 1.1 `cursor_preturi` — lista de preturi, ramura principala (`:2138-2644`)
|
||||
|
||||
```
|
||||
PROCEDURE cursor_preturi(V_DATA_CURS IN DATE, V_TIP IN NUMBER, V_ID_VALUTA IN NUMBER,
|
||||
V_ID_GESTIUNE_INIT IN NUMBER, V_LUNA IN NUMBER, V_AN IN NUMBER,
|
||||
V_ID_UTIL IN NUMBER, V_ID_SUCURSALA IN NUMBER,
|
||||
V_CURSOR OUT cursor_facturare)
|
||||
```
|
||||
|
||||
**Fara filtru pe articol** — semnatura n-are niciun parametru de cod/denumire. Ramuri pe `V_TIP`
|
||||
(`CASE`, `:2159-2643`):
|
||||
|
||||
| Ramura | Cand | Sursa randurilor | Observatie |
|
||||
|---|---|---|---|
|
||||
| `V_TIP = 45` | restaurant | `utilizatori_rol_intern` -> `politici_grupuri` -> `crm_politici_preturi` -> `crm_politici_pret_art` -> `nom_articole`, plus `curs`/`nom_valute` | fara filtru de stoc, `cantitate` fixa la 1 (`:2193`) |
|
||||
| `V_TIP IN (1,2)` | factura in lei | acelasi lant de politici, plus `LEFT JOIN` pe `STOC` agregat pe gestiunile utilizatorului | `WHERE` final filtreaza pe stoc > 0 sau `RF_FACTURARE_FARA_STOC` (`:2387-2388`) |
|
||||
| `V_TIP IN (5,6,10,52)` | factura in valuta | `FACT_VPRETURI_UTILIZATOR` (view precalculat, nu politici brute) + `STOC` + `CURS` | `WHERE A.ID_UTIL = V_ID_UTIL AND ((A.ID_VALUTA = V_ID_VALUTA AND A.IN_VALUTA=1) OR A.ID_POL = politica_stoc)` (`:2461-2462`) |
|
||||
| `V_TIP = 7` | credit note | `FACT_VPRETURI_UTILIZATOR`, filtrat suplimentar `NVL(A.nota_discount,0)=1` (`:2538`) | doar articole de discount |
|
||||
| `ELSE` (aviz, tip implicit) | orice alt tip | `FACT_VPRETURI_UTILIZATOR`, fara filtrul de valuta din ramura 5/6/10/52 | ramura cea mai generala |
|
||||
|
||||
Coloane comune returnate (uniforme pe toate ramurile, ceea ce conteaza pentru mapare — punctul 6):
|
||||
`id_c, id_articol, lot, serie, id_pol, id_valuta, nume_lista_preturi, discount_unitar,
|
||||
discount_unitar_val, codmat, codbare, denumire, um, gestionabil, cantitate, proc_tvav,
|
||||
preturi_cu_tva, curs, multiplicator, pret, pret_val, tip_valuta, nume_val` (+ `modificabil`,
|
||||
`id_gestiune`, `cont` doar pe ramura restaurant).
|
||||
|
||||
Apeleaza intai `initializeaza_facturare`, `completare_politica_stoc` si
|
||||
**`verifica_cursuri_valute(V_DATA_CURS, V_ID_UTIL)`** (`:2149-2153`) — validare **neconditionata**
|
||||
a cursurilor pentru toate valutele din listele de preturi ale utilizatorului, indiferent de articolul
|
||||
cautat. Relevant pentru S4d (`-20005`).
|
||||
|
||||
### 1.2 `cursor_contract` — factura/aviz pe contract (`:2646-2950`)
|
||||
|
||||
```
|
||||
PROCEDURE cursor_contract(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_LISTAID, V_ID_GESTIUNE_INIT,
|
||||
V_LUNA, V_AN, V_ID_UTIL, V_ID_SUCURSALA,
|
||||
V_ID_AGENT OUT, V_NUME_AGENT OUT,
|
||||
V_CURSOR OUT cursor_facturare, V_CURSOR2 OUT cursor_facturare)
|
||||
```
|
||||
|
||||
**Doua cursoare de iesire.** `V_CURSOR2` (`:2718-2938`) e specific contractului: `UNION ALL` intre
|
||||
(a) articole cu `OPT_FACTURARE = 3` din `CTR_ARTICOLE` si (b) rate din `CTR_SCADENTAR` pentru
|
||||
`OPT_FACTURARE IN (1,2)`, cu `id_articol = NULL` pe randurile de rata — filtrate pe
|
||||
`V_LISTAID` (lista de `id_ctr`) prin `charn2collection`. La final (`:2940-2948`) **cheama
|
||||
`cursor_preturi` cu aceiasi parametri** si scrie rezultatul in `V_CURSOR` — deci jumatate din
|
||||
`cursor_contract` e literalmente `cursor_preturi`.
|
||||
|
||||
In VFP, cele doua cursoare de iesire ajung (observat pe cod, nu documentat explicit in `goExecutor`)
|
||||
in `crsarticole` (=`V_CURSOR`, lista de preturi) si `crsarticole1` (=`V_CURSOR2`, liniile contract +
|
||||
rate) — vezi `ofacturare.vc2:13778` (`lcCursorStoc = Iif(poArticol.opt_facturare = 0, [crsarticole],
|
||||
[crsarticole1])`) si `Destroy` (`:12804-12811`) care inchide ambele.
|
||||
|
||||
**Randurile de rata nu au `id_articol`** — nu pot fi gasite printr-o cautare cod/denumire; raman
|
||||
legate de gridul `crsarticole1` existent (`grd_contracte`), bounded de numarul de rate ale
|
||||
contractului, nu de catalog. Vezi punctul 7.
|
||||
|
||||
Valideaza cursurile valutare **doar** pentru valutele prezente in `CTR_SCADENTAR`/`CTR_ARTICOLE` ale
|
||||
contractelor din `V_LISTAID` (`:2679-2716`) — spre deosebire de `cursor_preturi`, care valideaza tot
|
||||
ce are utilizatorul in politici. Filtru deja ingust pe sursa, dar tot pe tot contractul, nu pe
|
||||
articol.
|
||||
|
||||
### 1.3 `cursor_comanda` — factura/aviz pe comanda (`:2952-3171`)
|
||||
|
||||
```
|
||||
PROCEDURE cursor_comanda(V_DATA_CURS, V_TIP, V_LISTAID, V_ID_UTIL, V_CURSOR OUT cursor_facturare)
|
||||
```
|
||||
|
||||
`V_LISTAID` e **un singur `id_comanda`** (`TO_NUMBER(V_LISTAID)`, `:2963` — nu o lista, desi
|
||||
parametrul se numeste la fel ca la contract/avize). Doua ramuri identice ca forma (`V_TIP <= 20` =
|
||||
factura, altfel aviz, `:2994-3170`), ambele pe `COMENZI_ELEMENTE` filtrat `WHERE A.ID_COMANDA =
|
||||
V_ID_COMANDA` (`:3078`, `:3166`) — **deja filtrat pe un singur document sursa**, nu pe tot catalogul.
|
||||
`LEFT JOIN` cu suma cantitatilor deja facturate din `VANZARI_DETALII` pe acelasi `ID_COMANDA`
|
||||
(`:3060-3068`) calculeaza `cantitate` ca **ramas de facturat**, nu cantitatea comandata bruta —
|
||||
exact sursa pentru bookkeeping-ul de "adauga tot" / stergere de la punctul urmator.
|
||||
|
||||
### 1.4 `cursor_avize` — factura din avize (`:3703-3874`)
|
||||
|
||||
```
|
||||
PROCEDURE cursor_avize(V_LISTAID, V_ID_UTIL, V_DISCOUNT OUT NUMBER, V_CURSOR OUT cursor_facturare)
|
||||
```
|
||||
|
||||
`V_LISTAID` = lista de `id_vanzare` (avize sursa), separate prin virgula. O singura interogare,
|
||||
fara `CASE` pe tip — agrega `VANZARI_DETALII` pe cheie compusa (articol, pol, lot, serie, discount,
|
||||
TVA, gestiune, cont, pret...) si scade ce a fost deja facturat din `VANZARI_CANTITATI`
|
||||
(`WHERE A.CANTITATE <> NVL(B.CANTITATE, 0)`, `:3871`) — acelasi tipar "ramas de facturat" ca la
|
||||
comanda. **Nu are ramuri pe `V_TIP`** — planul o citeaza ca exemplu de "aceleasi ramuri pe tip", dar
|
||||
real e cea mai simpla dintre cele cinci: un singur `SELECT`.
|
||||
|
||||
### 1.5 `cursor_gestiune` — transfer intre subunitati pe lista de preturi (`:4158-4310+`)
|
||||
|
||||
```
|
||||
PROCEDURE cursor_gestiune(V_DATA_CURS, V_ID_POL, V_ID_GESTIUNE, V_LUNA, V_AN,
|
||||
V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR OUT cursor_facturare)
|
||||
```
|
||||
|
||||
Foloseste `V_ID_POL` **fix** (nu politica derivata din utilizator ca la `cursor_preturi`), pe
|
||||
`STOC` filtrat `ID_GESTIUNE = V_ID_GESTIUNE` `JOIN` `CRM_POLITICI_PRET_ART WHERE ID_POL = V_ID_POL`
|
||||
(`:4266-4280`) — deja restrans la o singura gestiune si o singura politica, dar tot fara filtru pe
|
||||
articol; `GROUP BY` pe articol agrega stocul.
|
||||
|
||||
## 2. De unde sunt executate; cine consuma `crsarticole`
|
||||
|
||||
**Executia:** un singur loc, `ofacturare.prg:266-311` (`factureaza`), rutare pe `tnTip` (punct de
|
||||
plecare confirmat, `Do Case` la `:266-308`) -> `goExecutor.oExecute(lcSqlCursor, [crsarticole])`
|
||||
(`:310-311`). Acelasi tipar apare a doua oara in `factureaza2` (`ofacturare.prg:824+`, ramura
|
||||
`frm_facturare_articole2`, prototipul).
|
||||
|
||||
**Consumatorii `crsarticole` in perimetrul S4** (`frm_facturare_articole`, `ofacturare.vc2:11xxx-15300`;
|
||||
lista completa via `vfp_symbols -Grep 'crsarticole\b' -CodeOnly`):
|
||||
|
||||
| Metoda | Linii | Ce face cu `crsarticole` | Ramane dupa S4? |
|
||||
|---|---|---|---|
|
||||
| `Destroy` | `12804-12811` | inchide cursorul | da, neschimbat |
|
||||
| `do_adauga_articol` | `12813-13167` | citeste **un rand ales** (`Scatter Name poArticol`), il scrie in `crsfactura` | **inlocuit** de randul intors de cautarea pe server, pe ramurile de lista de preturi — vezi punctul 7 |
|
||||
| `do_adauga_tot` | `13169-13198` | `Scan` peste tot cursorul, cheama `do_adauga_articol` pe fiecare rand | **pastrat neschimbat**, dar numai pe tipurile cu document sursa |
|
||||
| `do_cauta` | `13566-13593` | filtru client-side (`Set Filter`) peste cursorul deja incarcat, dupa text tastat in `txtArticole`/`txtCodmat` si politica aleasa | **dispare** pe ramurile de lista de preturi — inlocuit de `combosql` |
|
||||
| `do_scrie_factura` | `14301-14338` | `Calculate Sum(cantitate) To lnCantitateRamasa` peste `crsarticole`, decide daca se inchide comanda/avizul sursa (`pnParametruAditional`) | **pastrat neschimbat** — vezi verdictul |
|
||||
| `do_sterge` | `14608-14669` | la stergerea unei linii din `crsfactura`, **reface** cantitatea in `crsarticole`/`crsarticole1` (`Replace cantitate With cantitate + poArticol.cantitate`) | **pastrat neschimbat** pe tipurile cu bookkeeping; pe lista de preturi nu exista azi replace-back real (cantitatea nu scade la adaugare pe acele tipuri — stocul se verifica separat, prin `cursor_gestiuni_articol*`) |
|
||||
| `do_modifica` | `13778` | alege `crsarticole` sau `crsarticole1` dupa `opt_facturare` pentru verificare stoc | pastrat, tine de gestiunea aleasa la editarea unei linii deja adaugate |
|
||||
| `cb_politici_preturi.InteractiveChange` | `15647-15658` | cheama `do_cauta()` (filtru client-side) | **dispare** — pe varianta noua, schimbarea politicii devine parametru al cautarii pe server (`id_pol` in `WHERE`), nu filtru local |
|
||||
| `KeyPress` (navigare grid) | `15463-15486` | navigare in cursor la taste sageata | **dispare** pe ramurile fara grid de sus |
|
||||
|
||||
`frm_facturare_articole2` (prototipul, `:17xxx-18xxx`) are exact aceeasi structura pe
|
||||
`do_adauga_articol` / `do_adauga_tot` / `do_scrie_factura` / `do_sterge` — nu difera in aceasta
|
||||
privinta.
|
||||
|
||||
**Concluzie pentru cat se poate scoate:** `do_cauta` si filtrul din `cb_politici_preturi` dispar
|
||||
integral (inlocuite de cautarea pe server). `do_adauga_articol` isi schimba sursa randului (de la
|
||||
`Scan` in cursorul local la randul ales de `combosql`), dar restul lui (verificare stoc, alegere
|
||||
gestiune via `cursor_gestiuni_articol*`, scriere in `crsfactura`) **nu se schimba** — vezi punctul 6.
|
||||
`do_adauga_tot`, `do_sterge` (partea de bookkeeping) si `do_scrie_factura` (`lnCantitateRamasa`)
|
||||
**nu pot fi scoase** cat timp `crsarticole` ramane sursa lor de adevar pentru "cat a mai ramas" —
|
||||
motiv suplimentar, nu doar UX, pentru care "adauga tot" ramane cablat pe incarcare in masa acolo
|
||||
unde exista document sursa.
|
||||
|
||||
## 3. Cat costa azi incarcarea, pe forma interogarii (nu pe timpi masurati — decizia 31)
|
||||
|
||||
**`cursor_preturi` (ramurile 1/2 si generala) nu are niciun filtru pe articol** — semnatura n-are
|
||||
parametru de cod/denumire (`:2138-2146`). Rezultatul e **intreg catalogul accesibil utilizatorului**:
|
||||
`utilizatori_rol_intern` -> `politici_grupuri` -> `crm_politici_preturi` (toate politicile active la
|
||||
data cursului) -> `crm_politici_pret_art` (toate liniile de pret ale acelor politici) -> `nom_articole`.
|
||||
Numarul de randuri creste cu produsul dintre "cate politici de pret vede utilizatorul" si "cate
|
||||
articole are fiecare politica" — nu cu numarul de articole cautate, care e de regula 1.
|
||||
|
||||
Trei surse structurale de cost, vizibile direct in forma interogarii, nu masurate:
|
||||
|
||||
1. **`LEFT JOIN` pe `STOC` agregat** (`:2359-2378`, ramura 1/2) — subquery cu `GROUP BY ID_ARTICOL`
|
||||
peste tot stocul lunii curente, pe toate gestiunile la care utilizatorul are drept, recalculat la
|
||||
fiecare deschidere de formular, indiferent daca utilizatorul cauta un singur articol.
|
||||
2. **`FACT_VPRETURI_UTILIZATOR`** (ramurile 5/6/10/52 si 7 si aviz) e un view, nu un tabel — planul
|
||||
nu detaliaza corpul lui aici (ar fi o cercetare separata), dar fiind sursa pentru "toate preturile
|
||||
vizibile utilizatorului", are aceeasi forma: cost proportional cu marimea catalogului, nu cu
|
||||
cautarea.
|
||||
3. **`verifica_cursuri_valute`** (`:2153`, chemata necondiționat la fiecare apel) valideaza cursul
|
||||
pentru **toate** valutele din politicile utilizatorului, nu doar valuta articolului cautat —
|
||||
cost fix per deschidere, independent de ce se cauta.
|
||||
|
||||
Concluzia structurala: interogarea de azi calculeaza raspunsul pentru "orice articol ar putea alege
|
||||
utilizatorul", cand formularul are nevoie doar de "articolul pe care tocmai l-a tastat". Filtrarea pe
|
||||
`id_articol`/`codmat`/`denumire` reduce fiecare din cele trei surse la un numar de randuri marginit
|
||||
de cate politici de pret contin acel articol (de regula 1, rar cateva), nu de marimea catalogului.
|
||||
|
||||
## 4. Proiectarea variantei filtrate, cursor cu cursor
|
||||
|
||||
Toate cinci sunt proceduri PL/SQL in `PACK_FACTURARE`, apelate direct din VFP prin
|
||||
`{call pack.proc(...)}` + `goExecutor.oExecute`, fara view intermediar si fara strat ORM — deci
|
||||
minimul de schimbare e **acelasi tipar**: o procedura noua (sau o supraincarcare cu parametru
|
||||
suplimentar) in acelasi pachet, apelata din acelasi loc (`ofacturare.vc2`, metoda nou-introdusa pe
|
||||
`combosql`, nu din `ofacturare.prg`, care ramane neschimbat — el tot incarca varianta "in masa"
|
||||
acolo unde ramane necesara).
|
||||
|
||||
**Alegere de proiectare, nu fapt verificat:** supraincarcare (aceeasi denumire, parametru nou
|
||||
opțional `V_FILTRU_COD IN VARCHAR2 DEFAULT NULL` / `V_FILTRU_DEN IN VARCHAR2 DEFAULT NULL`) e mai
|
||||
sigura decat o procedura noua, pentru ca **garanteaza** aceleasi ramuri de `CASE`, acelasi `JOIN`,
|
||||
aceeasi logica de rotunjire — orice divergenta viitoare intre "cursor complet" si "cursor filtrat" ar
|
||||
fi un bug de sincronizare greu de prins. Cu supraincarcare, filtrul se adauga o singura data, in
|
||||
`WHERE`-ul final al fiecarei ramuri, nu in logica de business.
|
||||
|
||||
| Cursor | Ce se adauga | Unde (linia `WHERE`/`CASE` care primeste filtrul) |
|
||||
|---|---|---|
|
||||
| `cursor_preturi` | `AND (V_FILTRU_COD IS NULL OR C.CODMAT LIKE V_FILTRU_COD) AND (V_FILTRU_DEN IS NULL OR UPPER(C.DENUMIRE) LIKE UPPER(V_FILTRU_DEN))` — pe alias-ul articolului, `C` pe patru din cinci ramuri, `A` pe ramurile cu `FACT_VPRETURI_UTILIZATOR` | dupa `WHERE` existent, pe fiecare din cele 5 ramuri (`:2264`, `:2387-2388`, `:2464-2465`, `:2540-2541`, `:2640-2641`) — 5 locuri, nu unul |
|
||||
| `cursor_contract` | acelasi filtru pe `V_CURSOR` (delegat catre `cursor_preturi`, gratuit); pe `V_CURSOR2` filtrul se adauga in `UNION ALL`-ul de la `:2752` (partea cu `id_articol`), **nu** pe partea de rate (`id_articol IS NULL` — nu se pot filtra pe cod, raman needitate de filtru) | `:2825` (join articol) + propagare in `WHERE`-ul de la `:2819` |
|
||||
| `cursor_comanda` | filtru pe `C.CODMAT`/`C.DENUMIRE` in `WHERE A.ID_COMANDA = V_ID_COMANDA` (`:3078`, `:3166`) — **dar nu are sens sa se filtreze**: cf. punctul 7, comanda ramane pe calea "adauga tot" | de proiectat doar daca decizia de la punctul 7 se schimba |
|
||||
| `cursor_avize` | idem — nu are sens, acelasi motiv | idem |
|
||||
| `cursor_gestiune` | filtru pe `C.CODMAT`/`C.DENUMIRE` in interogarea de la `:4234-4237`, inainte de `GROUP BY` | `:4266-4293` |
|
||||
|
||||
**Parametrii care raman identici** pe varianta filtrata: `V_DATA_CURS`, `V_TIP`, `V_ID_VALUTA`,
|
||||
`V_ID_GESTIUNE_INIT`, `V_LUNA`, `V_AN`, `V_ID_UTIL`, `V_ID_SUCURSALA` — tot ce vine din `poDate` /
|
||||
sesiune la deschiderea formularului, nu se recalculeaza per cautare. Doar filtrul de text e nou, plus
|
||||
(pentru `cursor_preturi` in noua utilizare) eventual `V_ID_POL` daca utilizatorul a ales explicit o
|
||||
lista de preturi in combo-ul `cb_politici_preturi` — azi acel combo doar filtreaza local
|
||||
(`ofacturare.vc2:15647-15658`, comentat inlocuit cu `do_cauta()`); pe varianta noua devine parametru
|
||||
real al interogarii.
|
||||
|
||||
**Ce nu se poate verifica din cod, ramane de decis de Marius:** daca filtrul pe `codmat` foloseste
|
||||
`LIKE` "incepe cu" (ca `combosql` de azi, `nCharCountBegin`/cautare incrementala) sau match exact la
|
||||
alegerea din lista — combosql de azi face amandoua (tastare = "incepe cu" pe server, alegere din
|
||||
lista = valoare exacta), deci varianta cea mai apropiata de comportamentul actual e sa pastreze
|
||||
acelasi tipar: interogarea filtrata se cheama la fiecare tastare (ca azi, `RefreshData`), iar
|
||||
valoarea finala aleasa vine din randul deja adus, nu dintr-o interogare separata "exact match".
|
||||
|
||||
## 5. `combosql` — contract si exemple reale
|
||||
|
||||
**Clasa:** `combosql AS combobox`, `COMUN\clase\_cb_base.vc2:519-843`. Nu e specifica facturarii —
|
||||
traieste in biblioteca comuna a suitei, dar cautarea nu a gasit nicio alta utilizare, nici in
|
||||
`ROAFACTURARE`, nici in `COMUNROA`/`ROAGEST` (`grep -rn "AS combosql WITH"` — zero potriviri in afara
|
||||
`ofacturare.vc2:16751/16785/16861`, cele trei coloane ale gridului prototip). **Singurul exemplu real
|
||||
de folosire e chiar prototipul din plan** — nu exista alt loc in suita de copiat.
|
||||
|
||||
**Proprietati relevante** (`_memberdata`, `:560-575`):
|
||||
|
||||
| Proprietate | Rol |
|
||||
|---|---|
|
||||
| `csourcesql` | `SELECT` fara `WHERE`/`ORDER BY` — baza interogarii |
|
||||
| `csourcewhere` | conditia `WHERE` fixa (ex. `inactiv = 0`) |
|
||||
| `csourceorder` | `ORDER BY`, implicit `pfieldactiv` |
|
||||
| `pcursorname` | numele cursorului cu rezultatele |
|
||||
| `pfieldactiv` | campul dupa care se cauta si se afiseaza |
|
||||
| `psecondfield` | al doilea camp de cautare (optional) |
|
||||
| `ncharcountbegin` | cate caractere minim inainte sa porneasca interogarea pe server |
|
||||
| `llimittolist` | daca valoarea trebuie sa existe in lista |
|
||||
|
||||
**Mecanismul** (`refreshdata`, `:796-830`): construieste
|
||||
`SELECT ... FROM (cSourceSql) WHERE (cSourceWhere) AND (pFieldActiv LIKE ?pcValue [OR psecondfield
|
||||
LIKE ?pcValue]) ORDER BY ...`, cu `?pcValue` = `cSearchString + '%'` (incepe cu) sau
|
||||
`'%'+cSearchString+'%'` (contine, la Ctrl+Enter) si il executa prin
|
||||
**`goExecutor.oExecuta`** (`selectdata`, `:832-840`) — acelasi executor folosit peste tot in suita
|
||||
pentru apeluri Oracle, deci **niciun mecanism nou de transport**, doar un SQL nou de trimis.
|
||||
`KeyPress` (`:610-776`) gestioneaza incremental tastarea: la fiecare caracter tastat re-executa
|
||||
`RefreshData(1)` daca lungimea depaseste `nCharCountBegin`.
|
||||
|
||||
**Contractul de legare la o coloana de grid**, dedus din prototip (`ofacturare.vc2:16751-16762` +
|
||||
`:19288-19306`):
|
||||
|
||||
1. se adauga ca `CurrentControl` al coloanei (`Column1.CurrentControl = "cCboDenumire"`, `:16621`);
|
||||
2. `ControlSource` leaga afisarea de campul din cursorul de linii (`crsFactura.codmat`, `:16754`);
|
||||
3. **evenimentul de reactie e `LostFocus`, nu `Valid` sau `InteractiveChange`** — abia la parasirea
|
||||
celulei se scriu campurile in cursorul de linii, cu paza `If this.Value <> thisform.cOldValue`
|
||||
(setat in `When`, `:19308-19310`) ca sa nu se rescrie fara motiv;
|
||||
4. `LostFocus` de azi scrie **doar** campurile pe care le are `vnom_articole`: `codmat`, `denumire`,
|
||||
`id_articol` (`:19293`) — nimic despre pret, TVA, valuta. **Aici se opreste prototipul azi**; tot
|
||||
ce trebuie adaugat pentru S4 e sa inlocuiasca `crsCodmat`/`crsDenumire` (rezultatul din
|
||||
`vnom_articole`) cu rezultatul cursorului Oracle filtrat de la punctul 4, care are toate coloanele,
|
||||
si sa extinda `REPLACE`-ul de la `:19293`/`:19317` cu restul campurilor din tabelul de la punctul 6.
|
||||
|
||||
**Ce nu ofera azi `combosql` si trebuie adaugat, nu doar copiat:** `csourcesql` e un `SELECT`
|
||||
static definit in `.vcx` la design-time, nu poate primi parametrii dinamici ai documentului curent
|
||||
(`poDate.zi_curs`, `poDate.tip`, `poDate.id_valuta`...) direct in proprietate. Pentru cursorul
|
||||
Oracle cu 8 parametri, populate din `poDate`/sesiune, `csourcesql`/`csourcewhere` trebuie construite
|
||||
dinamic in `Init` sau la schimbarea documentului (nu sunt string-uri fixe ca azi), sau — alternativa
|
||||
mai simpla — `refreshdata`/`selectdata` se suprascriu punctual pe instanta din grid, ca sa apeleze
|
||||
`{call pack_facturare.cursor_preturi_filtrat(...)}` in loc de `SELECT ... FROM (cSourceSql)`. A doua
|
||||
varianta pastreaza restul mecanismului (`KeyPress`, incremental, `LostFocus`) neschimbat si e
|
||||
schimbarea minima — de confirmat cu Marius, nu o certitudine de cod.
|
||||
|
||||
## 6. Maparea camp-cu-camp
|
||||
|
||||
Sursa cea mai fiabila pentru "ce completeaza azi `crsarticole` in linie" nu e cursorul Oracle brut, ci
|
||||
`Gather Name poArticol Fields Like ...` din `do_adauga_articol` (`ofacturare.vc2:12945-12957`,
|
||||
identic pe `frm_facturare_articole2` la `:17226+`) — acolo se vede exact ce trece din randul scanat
|
||||
in `crsfactura`.
|
||||
|
||||
| Camp in `crsfactura` | Vine din `crsarticole` (coloana cursorului Oracle) | Pe varianta filtrata |
|
||||
|---|---|---|
|
||||
| `id_articol`, `codmat`, `codbare`, `denumire`, `um` | direct din cursor | identic — filtrul e chiar pe aceste coloane |
|
||||
| `pret_achizitie` | **nu** din `crsarticole` — vine din `poArtLista`/`crsartselectate` (rezultatul lui `cursor_gestiuni_articol*`, ales dupa `crsarticole`), nu din cursorul de lista de preturi | **neschimbat** — pasul e deja separat azi, ramane separat |
|
||||
| `id_pol` | direct (`crsarticole.id_pol`) | identic |
|
||||
| `Cont` | direct (`crsarticole.cont`, doar ramura restaurant o are explicit; pe restul vine `'371'` implicit sau din `cursor_gestiuni_articol*`) | identic pe ramurile care il au; **de verificat** pe ramurile 1/2/5/6/7/10/52/aviz — cursorul nu are `CONT` explicit in `SELECT` (`:2268-2329` etc.), deci provine din `do_alege_stoc`, nu din `crsarticole` |
|
||||
| `id_gestiune` | direct doar pe ramura restaurant (`A.ID_GESTIUNE`, `:2220`); pe rest vine din alegerea de gestiune (`cursor_gestiuni_articol*`) | identic — pasul de alegere gestiune ramane neschimbat |
|
||||
| `Proc_Tvav`, `pretftva`, `Pretctva`, `discountftva`(`discount_unitar`), `discountctva` | direct din cursor, plus `do_initializeaza_articol` (`:13618-13659`) care deriva `pretftva`/`pretctva`/`tva` reciproc dupa `preturi_cu_tva` | identic — logica de derivare e in VFP, nu se schimba |
|
||||
| `id_valuta`, `tip_valuta`, `nume_val`, `Curs`, `multiplicator` | direct din cursor | identic |
|
||||
| `gestionabil` | direct din cursor | identic (inclusiv regula de proforma, `ofacturare.prg:333-336`, care suprascrie `gestionabil=0` — ramane in `ofacturare.prg`, neschimbata) |
|
||||
| `id_jtva_coloana` | direct doar pe ramurile care il au explicit in `SELECT` (aviz/contract/retur); pe `cursor_preturi` (1/2/5/6/7/10/52) **nu apare in lista de coloane** returnate (`:2264-2329`) | **de verificat cu Marius** — daca lipseste azi, `Gather ... id_jtva_coloana` scrie `NULL`/valoare implicita; filtrarea nu schimba nimic aici, dar merita clarificat inainte de implementare, nu presupus |
|
||||
| `pretv_orig`, `pretd`, `id_valuta_d` | nu apar in `cursor_preturi`; vin din `poArtLista`/`crsartselectate` (alegerea de gestiune) | neschimbat |
|
||||
| `taxcode`, `explicatie`, `id_lucrare_rez`, `id_part_rez`, `id_ctr`, `opt_facturare` | nu din `crsarticole` — din `poArticol`/context (`do_initializeaza_articol`, `do_alege_stoc`) | neschimbat |
|
||||
| `pret_cu_tva` (flag `preturi_cu_tva`) | direct din cursor (`A.PRETURI_CU_TVA` / `D.PRETURI_CU_TVA`) | identic |
|
||||
|
||||
**Concluzie pentru punctul 6 al cerintei:** nicio coloana din cele cerute explicit (pret, cota TVA,
|
||||
valuta, `id_pol`, `gestionabil`, `pret_cu_tva`) nu ridica probleme — toate vin direct din cursorul
|
||||
Oracle si filtrarea pe articol nu le atinge. Singurul semnal de atentie e `id_jtva_coloana`, absent
|
||||
din `cursor_preturi` insusi (posibil completat in alt pas, neverificat aici — iese din perimetrul
|
||||
"cele cinci cursoare", ar cere citirea intregului `do_adauga_articol`/`calculeaza_totaluri`).
|
||||
|
||||
## 7. „Adauga tot" — ce ramane si de ce (nu doar decizia din plan)
|
||||
|
||||
Planul spune "se pastreaza adauga tot pentru tipurile cu document sursa (comanda, aviz, contract) —
|
||||
acolo setul e marginit". Cercetarea (punctul 2) arata un motiv suplimentar, mai tare decat marimea
|
||||
setului: **bookkeeping-ul de cantitate ramasa** (`do_sterge`, `do_scrie_factura`) citeste si scrie
|
||||
direct in `crsarticole`, deci acel cursor **trebuie sa existe incarcat in memorie** cat timp
|
||||
formularul e deschis, indiferent cum alege utilizatorul liniile.
|
||||
|
||||
**Vizibilitatea de azi a butonului `but_urmator_tot1`** ("adauga tot"), confirmata pe cod
|
||||
(`ofacturare.vc2:15108-15248`, `Do Case poDate.tip`):
|
||||
|
||||
| Tip | Vizibil "adauga tot" azi | Sursa cursorului |
|
||||
|---|---|---|
|
||||
| proforma (`eProforma=1`) | da (`:15113`) | `cursor_preturi` sau alt cursor dupa tip, marcat negestionabil |
|
||||
| copiere (`lCopiere`) | da (`:15120`) | `cursor_preturi` (varianta copiere, `ofacturare.prg:464-473`) |
|
||||
| `1, 5, 7, 10` — lista de preturi | **nu** | `cursor_preturi` |
|
||||
| `2, 6` — contract | **nu** | `cursor_contract` |
|
||||
| `3` — comanda | da (`:15150`) | `cursor_comanda` |
|
||||
| `4` — avize | da (`:15166`) | `cursor_avize` |
|
||||
| `21, 28, 42, 47` — aviz din comanda | da (`:15177`) | `cursor_comanda` |
|
||||
| `22, 29` — aviz din lista de preturi | **nu** | `cursor_preturi` |
|
||||
| `23, 41` — transfer subunitati | **nu** menționat explicit vizibil | `cursor_gestiune` |
|
||||
| `25` — transfer din comanda | da (`:15215`) | `cursor_comanda` |
|
||||
| `26` — aviz din contract | **nu** | `cursor_contract` |
|
||||
| `8, 9` — retur | da (`:15240`) | `cursor_retur` (nu e in cele 5, iese din perimetru) |
|
||||
| `24` — aviz retur | da (`:15245`) | `cursor_retur` |
|
||||
|
||||
**Coincide exact** cu impartirea utila pentru S4: butonul e vizibil azi pe tipurile unde
|
||||
`do_adauga_tot` are sens pentru ca setul e mic **si** bookkeeping-ul de ramas il cere
|
||||
(comanda/aviz-din-comanda/retur), si e ascuns pe tipurile de lista de preturi (1/5/7/10, 22/29) si pe
|
||||
contract-lista-de-preturi (2/6/26) — exact ramurile pe care cautarea filtrata inlocuieste incarcarea
|
||||
in masa. **Contractul insa nu are azi "adauga tot" vizibil deloc** (nici pe partea de rate, nici pe
|
||||
partea de articole) — planul semnaleaza asta separat, la S4b, ca gol de acoperit (tipurile 2, 6, 26,
|
||||
52 lipsesc din conditiile de vizibilitate), nu ca ceva de pastrat.
|
||||
|
||||
**Concret, ce se schimba per tip:**
|
||||
- **Lista de preturi (1,5,7,10,22,29) si transfer pe lista (23,41,45,48,49):** `crsarticole` nu se
|
||||
mai incarca la deschidere; `combosql` cauta filtrat; "adauga tot" ramane ascuns (ca azi — n-are
|
||||
sens pe catalog intreg).
|
||||
- **Comanda (3,21,25,28,42,47) si avize (4):** `crsarticole` **ramane incarcat in masa**, neschimbat;
|
||||
`combosql` **nu se activeaza** pe aceste tipuri (sau, daca se activeaza pentru UX, cauta *in*
|
||||
cursorul deja incarcat, nu pe server — cautare locala, nu Oracle) pentru ca bookkeeping-ul de
|
||||
cantitate ramasa il cere oricum incarcat.
|
||||
- **Contract (2,6,26,52):** dublu — `crsarticole` (jumatatea `cursor_preturi`) trece pe cautare
|
||||
filtrata ca orice lista de preturi; `crsarticole1` (rate + articole `OPT_FACTURARE=3`) **ramane
|
||||
incarcat in masa**, bounded de contract, neschimbat de S4 (randurile de rata n-au `id_articol`,
|
||||
nu pot fi cautate).
|
||||
- **Retur (8,9,24):** in afara celor cinci cursoare cerute (`cursor_retur`/`cursor_retur_document`),
|
||||
proiectat separat la S4f — nesemnalat aici ca problema, doar exclus din perimetru.
|
||||
|
||||
## 8. Interactiunea cu S4b si S10
|
||||
|
||||
**Cu S4b (bara de butoane / meniul de adaugare):** S4b proiecteaza meniul `xmenu()` "Adauga
|
||||
articole" cu optiunile pe sursa (tot / alege), inclusiv acoperirea golului de pe contract (tipurile
|
||||
2/6/26/52). S4 ii da continutul concret pentru ramura "cauta in linie": pe tipurile de lista de
|
||||
preturi (unde S4b nu are nevoie de optiunea "tot" azi), linia de grid cu `combosql` **este** metoda
|
||||
de adaugare — nu exista un dialog separat de ales. Pe tipurile cu document sursa, S4 nu schimba
|
||||
nimic din ce proiecteaza S4b: meniul ramane cablat pe `do_adauga_tot`/alegere din `crsarticole`
|
||||
existent. Punctul de atingere real: daca S4b decide sa afiseze si pe contract un "adauga tot" (gol
|
||||
semnalat la punctul 7), acel "tot" trebuie sa opereze pe `crsarticole1` (rate+articole), nu pe
|
||||
`crsarticole` (lista de preturi) — cele doua cursoare raman distincte si dupa unificare.
|
||||
|
||||
**Cu S10 (pretul care nu trebuie re-derivat, `docs\cercetare\s10_pret_rederivat.md`):** verificarea
|
||||
S10 arata ca `adauga_articol_factura` **primeste pretul ca parametru** (`V_PRET_ACHIZITIE_TEMP` si
|
||||
restul) si nu-l recalculeaza, cu o singura exceptie tacuta: liniile de contract cu
|
||||
`OPT_FACTURARE = 3`, unde `CTR_ARTICOLE.PRET_UNITAR` **suprascrie** pretul trimis, necondiționat de
|
||||
sursa. Pentru S4, asta inseamna doua lucruri concrete:
|
||||
1. cursorul filtrat (`cursor_preturi`/`cursor_gestiune` cu filtru pe articol) se cheama **o singura
|
||||
data**, la selectarea liniei in `combosql` (`LostFocus`) — pretul obtinut atunci se scrie in
|
||||
`crsfactura` si **nu se re-interogheaza** la scriere, exact ca azi pe calea `crsarticole` completa;
|
||||
2. pe **contract**, indiferent daca linia a fost aleasa prin `crsarticole1` (needitat de S4) sau,
|
||||
ipotetic, printr-o cautare filtrata viitoare, riscul de suprascriere tacuta descris in S10 exista
|
||||
deja si S4 nu-l introduce si nu-l rezolva — il mosteneste neschimbat, pentru ca punctul de
|
||||
suprascriere e in `adauga_articol_factura` (scriere), nu in cursorul de cautare (citire).
|
||||
|
||||
## 9. Ordinea de executie in pasi
|
||||
|
||||
1. **Pas 1 — Oracle, `cursor_preturi` filtrat.** Supraincarcare cu `V_FILTRU_COD`/`V_FILTRU_DEN`
|
||||
opționale, aceleasi 5 ramuri, filtru adaugat in `WHERE`-ul fiecareia (punctul 4). *Gata cand:*
|
||||
apelat cu filtru gol, cursorul e identic randuri-cu-randuri (aceleasi coloane, aceleasi valori, pe
|
||||
fiecare din cele 5 ramuri de `V_TIP`) cu varianta actuala pe acelasi set de parametri — comparatie
|
||||
directa pe SQL, nu pe timp.
|
||||
2. **Pas 2 — Oracle, `cursor_gestiune` filtrat.** Acelasi tipar, un singur `SELECT`, mai simplu.
|
||||
*Gata cand:* idem, comparatie randuri-cu-randuri cu filtru gol.
|
||||
3. **Pas 3 — Oracle, `cursor_contract`, doar partea `V_CURSOR`.** Filtrul se propaga gratuit prin
|
||||
apelul catre `cursor_preturi` de la `:2940-2948`; nicio schimbare suplimentara necesara daca Pasul
|
||||
1 e facut corect. *Gata cand:* `cursor_contract` cu filtru gol produce acelasi `V_CURSOR` ca
|
||||
inainte de Pasul 1 (regresie, nu functie noua).
|
||||
4. **Pas 4 — VFP, `combosql` pe `cCodMat`/`cCboDenumire`.** Inlocuieste `csourcesql`/`SelectData` cu
|
||||
apelul la cursorul filtrat (punctul 5); extinde `LostFocus` (`:19288-19330`) sa scrie toate
|
||||
campurile din tabelul de la punctul 6, nu doar `codmat`/`denumire`/`id_articol`. *Gata cand:*
|
||||
alegerea unei linii in grid, pe un document de tip lista-de-preturi, produce in `crsfactura`
|
||||
aceleasi valori ca alegerea aceluiasi articol azi din `crsarticole` incarcat complet — comparatie
|
||||
pe date, punctul 10.
|
||||
5. **Pas 5 — VFP, oprirea incarcarii in masa pe tipurile vizate.** In `ofacturare.prg:266-311`, pentru
|
||||
`tnTip` din ramurile de lista de preturi (1,5,7,10,22,29,23,41,45,48,49) si jumatatea `cursor_preturi`
|
||||
a contractului, `goExecutor.oExecute` nu se mai cheama la deschidere — grid-ul porneste gol,
|
||||
`combosql` populeaza la cerere. *Gata cand:* deschiderea formularului pe aceste tipuri nu executa
|
||||
niciun apel `cursor_preturi`/`cursor_gestiune`/`cursor_contract` (verificabil in log-ul
|
||||
`goExecutor`/`WAIT WINDOW ... NOWAIT` din `combosql.selectdata`, care ramane singurul loc care
|
||||
cheama Oracle pentru articole).
|
||||
6. **Pas 6 — verificare "adauga tot" neatins.** Pe comanda/aviz/contract-rate, `do_adauga_tot`,
|
||||
`do_sterge`, `do_scrie_factura` raman neschimbate (Pasii 1-5 nu le ating codul). *Gata cand:*
|
||||
regresie manuala pe un document din comanda si unul din aviz — comportament identic cu azi.
|
||||
|
||||
*Depinde de:* S3 (planul o marcheaza asa; portarea antetului trebuie sa existe inainte, ca sa aiba
|
||||
`poDate` populat corect la deschiderea gridului).
|
||||
|
||||
## 10. Cum se verifica „aceleasi valori ca azi"
|
||||
|
||||
**Testabil pe date (nu doar pe cod), propunere concreta:** pentru fiecare tip reprezentativ (1 sau 5
|
||||
pentru lista de preturi, 2 pentru contract, 41 pentru transfer-gestiune), se alege un articol prezent
|
||||
in cursorul complet de azi si se compara randul din `crsarticole` (deschidere veche, neschimbata) cu
|
||||
randul intors de cursorul filtrat pe acelasi articol, aceiasi parametri (`zi_curs`, `id_valuta`,
|
||||
`id_gestiune_init`, utilizator) — comparatie coloana cu coloana, in Oracle direct (doua `SELECT`-uri,
|
||||
`MINUS` intre ele pe coloanele comune) sau in VFP prin doua cursoare deschise simultan. Se repeta pe
|
||||
un articol cu politica de discount, unul in valuta si unul fara stoc (`RF_FACTURARE_FARA_STOC`), ca
|
||||
sa acopere ramurile de `CASE` din punctul 1.
|
||||
|
||||
**Ce nu se poate testa headless — capcana cunoscuta** (`grid-coloane-nu-se-materializeaza-headless`,
|
||||
memorie de proiect): sub `-A -T`, `ColumnCount`/`RecordSource` ale gridului sunt artefacte, nu
|
||||
reflecta ce vede operatorul. Deci **partea VFP** (alegerea din `combosql`, `LostFocus`-ul care scrie
|
||||
in `crsfactura`) nu se poate verifica prin harnessul headless obisnuit — cere fie harnessul UI
|
||||
vizibil existent, fie verificare directa pe cursorul `crsfactura` dupa un apel programatic la metoda
|
||||
`LostFocus` (posibil, pentru ca metoda insasi nu depinde de randare, doar de `This.Value` — de
|
||||
confirmat la implementare). Partea Oracle (comparatia de cursoare de mai sus) **e** testabila
|
||||
headless, prin `SELECT`, fara nicio dependenta de VFP.
|
||||
|
||||
## 11. Riscuri si ce ramane de decis de Marius
|
||||
|
||||
- **`id_jtva_coloana` lipsa din `cursor_preturi`** (punctul 6) — de clarificat inainte de
|
||||
implementare daca e completat in alt pas (nevazut aici) sau ramane `NULL` si azi; filtrarea nu
|
||||
schimba comportamentul, dar nota trebuie inchisa ca sa nu para un bug nou introdus de S4.
|
||||
- **Mecanismul de legare `combosql` -> cursor Oracle parametrizat dinamic** (punctul 5, ultimul
|
||||
paragraf) e o alegere de implementare, nu un fapt de cod — `csourcesql` static din `.vcx` nu poate
|
||||
primi parametrii de sesiune direct; solutia propusa (suprascriere punctuala `refreshdata`) e
|
||||
rezonabila, dar nu e singura posibila si nu exista alt exemplu in suita de comparat.
|
||||
- **Contractul ramane cu doua surse de date pe acelasi grid conceptual** (`crsarticole` filtrat +
|
||||
`crsarticole1` needitat) — de confirmat ca UX-ul (doua zone: cautare + grid rate) e acceptabil, nu
|
||||
doar tehnic corect.
|
||||
- **`cursor_gestiune` (transfer subunitati) nu are azi buton "adauga tot" vizibil** (tabelul din
|
||||
punctul 7 nu-l gaseste in `Do Case`) — de verificat separat daca tipurile 23/41 au alt mecanism de
|
||||
adaugare in masa, neacoperit de cautarea in `crsarticole`/`crsarticole1` directa; nu s-a citit codul
|
||||
suplimentar necesar pentru raspuns cert aici.
|
||||
- **Validarea de curs valutar (`verifica_cursuri_valute`, punctul 1.1) ramane neconditionata** chiar
|
||||
si pe varianta filtrata, daca se pastreaza in procedura supraincarcata — poate produce `-20005` la
|
||||
prima cautare a unui articol, inainte ca utilizatorul sa fi vazut vreun rand, pe o valuta pe care
|
||||
n-o foloseste azi doar pentru ca incarcarea in masa "o gasea" oricum. De discutat cu Marius daca
|
||||
validarea trebuie restransa la valuta articolului cautat sau ramane globala (comportament
|
||||
identic cu azi, doar declansat mai des — la fiecare cautare, nu o data la deschidere).
|
||||
- **Numarul exact de apeluri Oracle pe sesiune de cautare** (un apel per tastare, cu debounce prin
|
||||
`nCharCountBegin`) nu a fost masurat si nu trebuie masurat aici (decizia 31) — dar merita setat
|
||||
explicit `nCharCountBegin` >= 2-3 la implementare, ca sa nu se piarda avantajul filtrarii intr-un
|
||||
numar mare de interogari de o litera.
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare incheiata in aceasta sesiune, fara nevoie de predare — toate cele 11 puncte cerute sunt
|
||||
acoperite mai sus, cu citate `fisier:linie` verificate pe fisierul real (nu `.bak`). Niciun fisier de
|
||||
cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, nicio scriere pe Oracle.
|
||||
615
docs/cercetare/s4_punct2_registru_cantitate_ramasa.md
Normal file
615
docs/cercetare/s4_punct2_registru_cantitate_ramasa.md
Normal file
@@ -0,0 +1,615 @@
|
||||
# Cercetare — proiectare S4 punctul 2: decuplarea registrului de cantitate ramasa de `crsarticole`
|
||||
|
||||
Investigatie READ-ONLY pentru **punctul 2** al povestii S4 din
|
||||
`docs\plan_13_unificare_formular_facturare.md:2099-2113`. Continua raportul punctului 1
|
||||
(`docs\cercetare\s4_cautare_articole_server.md`) si corectiile din `docs\cercetare\s4_puncte_deschise.md`.
|
||||
Fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara scriere pe Oracle (numai
|
||||
`SELECT`). Nu ating `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`
|
||||
(perimetrul altei sarcini). **Decizia 39 (S4 ramane integrala, se desface registrul) nu e
|
||||
reargumentata** — proiectez CUM, nu DACA.
|
||||
|
||||
Sursa VFP: `COMUN\clase\ofacturare.vc2` (`frm_facturare_articole`; `frm_facturare_articole2`/prototip
|
||||
citit doar pentru confirmare, are aceeasi structura). Sursa Oracle:
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (corpul
|
||||
pachetului, `versiune_db.txt` = `2026_08_09_02`).
|
||||
|
||||
## Verdict (rezumat)
|
||||
|
||||
**Descoperirea centrala: `crsarticole.cantitate` are azi DOUA roluri distincte, nu unul, si numai
|
||||
unul din ele e "registrul de cantitate ramasa" pe care planul il numeste riscant.**
|
||||
|
||||
- **Rol A — cantitate ramasa de facturat dintr-un document sursa** (doar comanda: tip
|
||||
`3,21,25,28,42,47`; si avize: tip `4`). Cursoarele Oracle (`cursor_comanda`, `cursor_avize`) o
|
||||
**calculeaza deja live** la deschidere ca `cantitate_document - deja_facturat`. `do_scrie_factura`
|
||||
o re-suma din `crsarticole` (`Calculate Sum`) **doar ca sa decida ce sa trimita mai departe**
|
||||
(`pnParametruAditional`) — dar Oracle **recalculeaza acelasi lucru independent, din tabele reale**,
|
||||
in `inchide_comanda()` si `marcheaza_facturat()`, chiar in procedura care scrie factura. Cursorul
|
||||
VFP e o **copie redundanta a unui calcul pe care Oracle il repeta oricum la scriere** — exact
|
||||
problema "sursa unica" semnalata in misiune, si exact motivul pentru care decuplarea e posibila
|
||||
fara sa piarda paritatea: mutam citirea "cat a mai ramas" din cursorul local intr-un apel Oracle
|
||||
facut la momentul potrivit, nu intr-o a doua copie tinuta manual.
|
||||
- **Rol B — plafon de cantitate in sesiune** (lista de preturi gestionabila `1,22,29,2`-jumatate;
|
||||
transfer `23,41`; retur `8,9,24`). Decrementat/incrementat in `do_adauga_articol`/`do_modifica`/
|
||||
`do_sterge` ca sa nu lase operatorul sa adauge mai mult decat vede pe ecran, in aceeasi sesiune.
|
||||
**Nu alimenteaza nicio decizie Oracle** — nu apare in niciun `Calculate Sum` in afara celor doua
|
||||
locuri de la Rolul A, si Oracle nu-l citeste niciodata direct. E un plafon UI, nu un registru de
|
||||
business.
|
||||
- **Corectie fata de raportul punctului 1**: pasul 5 de acolo (`s4_cautare_articole_server.md:417-424`)
|
||||
propune oprirea incarcarii in masa pe tipurile `23,41` (printre altele), presupunand ca ele n-au
|
||||
bookkeeping de pastrat — dar **au Rol B** (confirmat pe cod, sectiunea 1 de mai jos). Punctul 1 nu
|
||||
poate fi aplicat pe `23,41` fara ca punctul 2 sa acopere intai Rolul B pe aceste doua tipuri.
|
||||
- **Recomandare de proiectare** (detaliata la sectiunea 4): pentru Rolul A, inlocuieste
|
||||
`Select crsarticole / Calculate Sum(cantitate)` cu un apel Oracle nou, la acelasi moment din
|
||||
`do_scrie_factura` (dupa `do_scrie_articole()`, cand `VANZARI_DETALII_TEMP` e deja populat), care
|
||||
reface exact interogarea pe care `inchide_comanda`/`marcheaza_facturat` o repeta oricum. Pentru
|
||||
Rolul B, plafonul devine un apel Oracle la cerere (aceeasi interogare care alimenteaza azi
|
||||
`cursor_preturi`/`cursor_comanda`/`cursor_gestiune`, filtrata pe articol), nu o valoare tinuta in
|
||||
memorie — nu mai e nevoie de sincronizare manuala pentru ca nu mai exista o a doua copie.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventar exact al scrierilor/citirilor de bookkeeping
|
||||
|
||||
Verificat direct pe `COMUN\clase\ofacturare.vc2` (nu pe `.bak`), linii confirmate cu `vfp_symbols.ps1`.
|
||||
|
||||
### 1.1 `do_adauga_articol` — scrierea la adaugarea unei linii (`:12813-13086`)
|
||||
|
||||
Dupa ce linia e scrisa in `crsfactura`, la `:13034-13051`:
|
||||
|
||||
```
|
||||
Select (lcCursor)
|
||||
lnRecno = Recno()
|
||||
Do Case
|
||||
Case gnScadereStoc = 1 And poDate.tip = 41 && aviz retur transfer catre subunitati lista pret
|
||||
Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c
|
||||
Go lnRecno
|
||||
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip, 3, 4, 21, 25, 28, 42, 47)
|
||||
Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c
|
||||
Go lnRecno
|
||||
Case Inlist(poDate.tip, 8, 9, 24) && factura retur lei si valuta, aviz retur
|
||||
Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c
|
||||
Go lnRecno
|
||||
Case poArticol.gestionabil = 1 And gnScadereStoc = 1 And ;
|
||||
(Inlist(poDate.tip, 1, 22, 29) Or (poDate.tip = 2 And poArticol.opt_facturare = 0))
|
||||
Replace cantitate With IIF(cantitate - poArticol.cantitate > 0, cantitate - poArticol.cantitate, 0) For id_articol = poArticol.id_articol And gestionabil = 1
|
||||
Go lnRecno
|
||||
Endcase
|
||||
```
|
||||
|
||||
`lcCursor` = `crsarticole1` daca `tlContract`, altfel `crsarticole` (`:12839-12845`).
|
||||
|
||||
### 1.2 `do_sterge` — refacerea la stergerea unei linii (`:14608-14677`)
|
||||
|
||||
```
|
||||
Select (lcCursor)
|
||||
lnRecNo = Recno()
|
||||
Do Case
|
||||
Case gnScadereStoc = 1 And poDate.tip = 41
|
||||
Select (lcCursor)
|
||||
Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c
|
||||
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip,3,4,21,25,28,42,47)
|
||||
Select (lcCursor)
|
||||
Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c
|
||||
Case Inlist(poDate.tip,8,9,24)
|
||||
Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c
|
||||
Case poArticol.id_gestiune <> - 1000 And gnScadereStoc = 1 And ;
|
||||
(Inlist(poDate.tip,1,22,29) Or (poDate.tip = 2 And poArticol.opt_facturare = 0))
|
||||
Select (lcCursor)
|
||||
Replace cantitate With cantitate + poArticol.cantitate For id_articol = poArticol.id_articol And gestionabil = 1
|
||||
Endcase
|
||||
```
|
||||
|
||||
`lcCursor` = `crsarticole1` daca `poArticol.opt_facturare <> 0`, altfel `crsarticole` (`:14615-14619`).
|
||||
**Exact inversul semnului fata de `do_adauga_articol`, pe aceleasi patru grupuri de tipuri** — simetrie
|
||||
confirmata, nu presupusa.
|
||||
|
||||
### 1.3 `do_modifica` — ajustarea la schimbarea cantitatii pe o linie deja adaugata (`:13746-13914`)
|
||||
|
||||
Doua interactiuni distincte cu registrul, in aceeasi metoda:
|
||||
|
||||
**(a) Calculul plafonului inainte de a arata dialogul** (`:13775-13798`), comentat explicit in cod
|
||||
ca "plafonul de cantitate = stocul disponibil reconstituit din cursorul sursa; fara randul in cursor
|
||||
ramane cantitatea liniei" (`:13775`):
|
||||
```
|
||||
lnCantitateMax = poArticol.cantitate
|
||||
lcCursorStoc = Iif(poArticol.opt_facturare = 0, [crsarticole], [crsarticole1])
|
||||
...
|
||||
Locate For id_c = poArticol.id_c
|
||||
If Found()
|
||||
Do Case
|
||||
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip, 4, 21, 28, 42, 47)
|
||||
lnCantitateMax = cantitate + poArticol.cantitate
|
||||
Case Inlist(poDate.tip, 8, 9, 24)
|
||||
lnCantitateMax = cantitate - poArticol.cantitate
|
||||
Endcase
|
||||
Endif
|
||||
```
|
||||
Aduna inapoi ce a consumat deja linia curenta, ca sa arate operatorului plafonul real (cat mai era
|
||||
disponibil **inainte** de aceasta linie), trimis ca parametru la `do_alege_stoc`/`frm_articol_factura`.
|
||||
|
||||
**(b) Ajustarea delta dupa ce operatorul schimba cantitatea** (`:13887-13904`):
|
||||
```
|
||||
lnDeltaCantitate = poArticol.cantitate - lnCantitateVeche
|
||||
If lnDeltaCantitate <> 0 And Used(lcCursorStoc)
|
||||
Do Case
|
||||
Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip, 4, 21, 28, 42, 47)
|
||||
Replace cantitate With cantitate - lnDeltaCantitate For id_c = poArticol.id_c
|
||||
Case Inlist(poDate.tip, 8, 9, 24)
|
||||
Replace cantitate With cantitate + lnDeltaCantitate For id_c = poArticol.id_c
|
||||
Endcase
|
||||
Endif
|
||||
```
|
||||
|
||||
**Gasit, nu presupus: `do_modifica` NU ajusteaza `crsarticole` pentru tipurile `1,22,29` si
|
||||
`2`-lista-jumatate** (grupul "gestionabil, plafon local" din `do_adauga_articol`/`do_sterge`) — doar
|
||||
pentru grupul comanda/aviz (`4,21,23,28,42,47`) si retur (`8,9,24`). E o asimetrie **preexistenta**
|
||||
in codul de azi (nu introdusa de S4): editarea cantitatii pe o linie de lista-de-preturi deja adaugata
|
||||
nu recalibreaza plafonul local. Nu e de reparat aici — doar de semnalat, ca varianta noua sa nu
|
||||
incerce sa reproduca o simetrie care nu exista azi.
|
||||
|
||||
### 1.4 `do_adauga_tot` — enumerare, nu bookkeeping propriu (`:13169-13198`)
|
||||
|
||||
```
|
||||
Select crsarticole
|
||||
Scan
|
||||
...
|
||||
Thisform.do_adauga_articol(.T.)
|
||||
...
|
||||
Endscan
|
||||
```
|
||||
|
||||
**Nu scrie direct in `crsarticole`** — cheama `do_adauga_articol` pe fiecare rand, care face
|
||||
bookkeeping-ul de la 1.1. Rolul lui `crsarticole` aici e al treilea, distinct de Rolul A/B: **sursa
|
||||
de enumerare** ("ce randuri exista de adaugat"), necesara oricum pentru populare (S4 punctul 1), nu
|
||||
doar pentru bookkeeping.
|
||||
|
||||
### 1.5 `do_scrie_factura` — citirea care alimenteaza decizia de inchidere (`:14301-14338`)
|
||||
|
||||
Doua ramuri, singurele doua locuri din intregul fisier unde `Calculate Sum(cantitate)` ruleaza peste
|
||||
`crsarticole` (confirmat cu grep pe tot fisierul, nicio a treia aparitie):
|
||||
|
||||
```
|
||||
Case poDate.Tip = 4
|
||||
Select crsarticole
|
||||
Calculate Sum(cantitate) To lnCantitateRamasa
|
||||
If lnCantitateRamasa > 0
|
||||
pnFacturaRetur = 7 - amessagebox("Doriti sa se inregistreze si avizul de retur?",4+32,"Confirmare")
|
||||
pnParametruAditional = 1
|
||||
Else
|
||||
pnFacturaRetur = 0
|
||||
pnParametruAditional = 0
|
||||
Endif
|
||||
...
|
||||
Case Inlist(poDate.Tip,3,21,25,28,42,47)
|
||||
Select crsarticole
|
||||
Calculate Sum(cantitate) To lnCantitateRamasa
|
||||
If lnCantitateRamasa <> 0
|
||||
pnParametruAditional = 7 - amessagebox("Doriti sa se inchida comanda?",4+32,"Confirmare")
|
||||
Endif
|
||||
...
|
||||
```
|
||||
|
||||
`pnParametruAditional` default e `0` (`:14222`, setat inainte de `Do Case`). Pentru **orice alt tip**
|
||||
(inclusiv `41`, `23`, `2/6/26/52`, `45/48/49`), ramane `0` fara sa fie calculat din `crsarticole` deloc
|
||||
(exceptie: tip `48` il suprascrie cu `poDate.coeficient_k`, fara legatura cu inchiderea — `:14367-14369`).
|
||||
|
||||
**Concluzie:** doar `4` si `Inlist(3,21,25,28,42,47)` sunt Rolul A. Restul tipurilor nu ating deloc
|
||||
aceasta parte a lui `do_scrie_factura` — inclusiv `41`/`23`, care au totusi bookkeeping Rol B activ la
|
||||
1.1-1.3.
|
||||
|
||||
---
|
||||
|
||||
## 2. Semantica registrului azi
|
||||
|
||||
### 2.1 Ce inseamna `cantitate` la incarcare, per grup
|
||||
|
||||
Confirmat pe corpul cursoarelor Oracle (`ff_...PACK_FACTURARE.sql`):
|
||||
|
||||
- **`cursor_comanda`** (`:2952-3171` per raportul punctului 1; verificat aici direct la sectiunea
|
||||
"aviz"/comanda a interogarii, `:3060-3081`): coloana `cantitate` e `A.CANTITATE - NVL(D.CANTITATE, 0)`
|
||||
unde `D` e `SUM(cantitate)` deja facturat din `VANZARI_DETALII` pe acelasi `id_comanda`
|
||||
(`:3060-3068`), filtrat `WHERE ... SIGN(A.CANTITATE) * (A.CANTITATE - NVL(D.CANTITATE, 0)) > 0`
|
||||
(`:3079`). **E deja "ramas de facturat", calculat live la deschidere** — nu cantitatea comandata
|
||||
bruta.
|
||||
- **`cursor_avize`** (`:3703-3874`): coloana `A.CANTITATE - NVL(D.CANTITATE, 0) AS CANTITATE`
|
||||
(analog la `:3118`), plus agregarea finala (`:3820-3872`) care scade `VANZARI_CANTITATI` deja
|
||||
consumat din `VANZARI_DETALII` (`WHERE A.CANTITATE <> NVL(B.CANTITATE, 0)`, `:3871`). Acelasi
|
||||
tipar: "ramas", nu cantitatea avizata bruta.
|
||||
- **Restul grupurilor (Rol B: `1,22,29,2`-lista, `23,41` transfer, `8,9,24` retur)**: `cantitate`
|
||||
vine din `cursor_preturi`/`cursor_gestiune`, care e **stoc disponibil** (`LEFT JOIN` pe `STOC`,
|
||||
raportul punctului 1 sectiunea 1.1/1.5), nu "ramas de facturat dintr-un document" — pentru ca aceste
|
||||
tipuri nu au un document sursa cu cantitati de urmarit (lista de preturi n-are document sursa;
|
||||
transferul/returul opereaza pe stoc, nu pe o comanda).
|
||||
|
||||
### 2.2 Cine o scade si cand
|
||||
|
||||
Vezi sectiunea 1: la fiecare `do_adauga_articol` (scade sau creste, dupa tip), la fiecare `do_sterge`
|
||||
(inversul), si la `do_modifica` doar pentru grupul Rol A + retur (2.1(b) de mai sus).
|
||||
|
||||
### 2.3 Ce se intampla la adaugare / stergere / modificare cantitate — rezumat comportamental
|
||||
|
||||
| Actiune | Rol A (comanda 3/21/25/28/42/47, aviz 4) | Rol B (lista gestionabila 1/22/29/2, transfer 23/41, retur 8/9/24) |
|
||||
|---|---|---|
|
||||
| Adauga linie | `crsarticole.cantitate` scade (creste la retur/41) cu cantitatea adaugata | idem, plafon local |
|
||||
| Sterge linie | reface exact simetric | reface exact simetric |
|
||||
| Modifica cantitate pe linie existenta | ajusteaza cu delta (23/4/21/28/42/47 si 8/9/24 explicit) | **nu ajusteaza** pe 1/22/29/2-lista (asimetrie preexistenta, 1.3) |
|
||||
| La scriere (`do_scrie_factura`) | **suma peste `crsarticole` decide `pnParametruAditional`** (inchidere) | **nu se suma niciodata** — nu alimenteaza nicio decizie Oracle |
|
||||
|
||||
---
|
||||
|
||||
## 3. Regula de inchidere automata — exact, cu dovada Oracle
|
||||
|
||||
### 3.1 Ce trimite VFP si ce face Oracle cu el
|
||||
|
||||
`pnParametruAditional` merge la Oracle prin `scrie_factura_avize` (tip `4`) sau `scrie_factura2`
|
||||
(toate celelalte, inclusiv comanda). Ambele cheama in interior `finalizeaza_factura(...)`
|
||||
(`ff_...PACK_FACTURARE.sql:14770-14852`), care are un `CASE` unic pe `pack_facturare.ntip` (setat
|
||||
intern din tipul documentului, nu parametru separat):
|
||||
|
||||
```sql
|
||||
CASE
|
||||
WHEN V_PARAMETRU_ADITIONAL = 1 AND pack_facturare.ntip IN (3, 21, 25, 28, 42, 47) THEN
|
||||
-- comanda: V_PARAMETRU_ADITIONAL este V_INCHIDERE_COMANDA
|
||||
pack_facturare.inchide_comanda();
|
||||
WHEN pack_facturare.ntip = 4 THEN
|
||||
-- aviz: V_PARAMETRU_ADITIONAL este V_VERIFICARE_FACTURAT
|
||||
pack_facturare.scrie_cantitati_vanzari_avize;
|
||||
pack_facturare.scrie_corespondente_vanzari(1);
|
||||
pack_facturare.marcheaza_facturat(V_PARAMETRU_ADITIONAL);
|
||||
WHEN pack_facturare.ntip = 24 THEN
|
||||
pack_facturare.scrie_corespondente_vanzari(2);
|
||||
pack_facturare.marcheaza_facturat(V_PARAMETRU_ADITIONAL);
|
||||
WHEN pack_facturare.ntip in (2, 6, 52) THEN
|
||||
pack_facturare.scrie_rate_factura(V_DATAORA);
|
||||
WHEN pack_facturare.ntip in (8, 9) THEN
|
||||
pack_facturare.scrie_corespondente_vanzari(3);
|
||||
ELSE
|
||||
dbms_output.put_line('---');
|
||||
END CASE;
|
||||
```
|
||||
(`:14818-14839`)
|
||||
|
||||
**`41` si `23` nu apar deloc in acest `CASE`** — cad pe `ELSE`, fara nicio actiune de inchidere.
|
||||
Confirma pe cod ce arata si sectiunea 1.5: transferul intre subunitati nu are un "document sursa" de
|
||||
inchis, doar un plafon de stoc (Rol B). `2/6/26/52` (contract) intra pe ramura `scrie_rate_factura`,
|
||||
care scrie scadentarul de rate — **nu e o "inchidere" in sensul comanda/aviz**, e alta operatie.
|
||||
|
||||
### 3.2 Comanda — `inchide_comanda()` (`:5769-5820`)
|
||||
|
||||
**Ruleaza doar cand `V_PARAMETRU_ADITIONAL = 1`** — adica doar daca `crsarticole` a aratat cantitate
|
||||
ramasa nenula **si** operatorul a raspuns "Da" la "Doriti sa se inchida comanda?" (`ofacturare.vc2:14336-14337`).
|
||||
Cand ramane 0 (fara diferenta), `inchide_comanda()` **nu se cheama deloc** — pentru ca o comanda complet
|
||||
livrata se raporteaza deja "inchisa" prin simplul fapt ca interogarea de mai jos nu mai returneaza
|
||||
randuri, fara nicio actiune suplimentara.
|
||||
|
||||
Ce face efectiv (`:5771-5818`): **insereaza un rand compensator** in `COMENZI_ELEMENTE`, cu
|
||||
`CANTITATE = ramas` (calculat live, independent de VFP, din `NVL(C.CANTITATE,0) + NVL(B.CANTITATE,0) -
|
||||
A.CANTITATE` unde `B` = deja facturat in aceasta tranzactie (`VANZARI_DETALII_TEMP`) si `C` = deja
|
||||
facturat istoric (`VANZARI`/`VANZARI_DETALII`)) — asta face ca o interogare ulterioara de tip
|
||||
`cursor_comanda` (aceeasi forma de `WHERE`, `SIGN(...) * (A.CANTITATE - NVL(D.CANTITATE,0)) > 0`) sa
|
||||
nu mai gaseasca randuri ramase, adica sa "inchida" comanda **prin recalcul, nu printr-un flag setat**.
|
||||
**Nu exista o coloana `INCHISA`/status persistat pe comanda** (cautare `INCHISA` in tot pachetul —
|
||||
zero potriviri) — starea "deschisa/inchisa" e intotdeauna derivata din suma `COMENZI_ELEMENTE` vs
|
||||
`VANZARI_DETALII`, nu citita dintr-un camp.
|
||||
|
||||
**Important pentru paritate:** cantitatea trimisa de VFP (`pnParametruAditional`, un simplu `1`/`0`)
|
||||
**nu participa la calculul cantitatii inserate** — Oracle o recalculeaza singur din tabele reale.
|
||||
Rolul VFP e strict binar: "a fost cerut sa se forteze inchiderea?".
|
||||
|
||||
### 3.3 Avize — `marcheaza_facturat(V_VERIFICARE)` (`:15381-15418`)
|
||||
|
||||
```sql
|
||||
IF V_VERIFICARE = 0 THEN
|
||||
UPDATE VANZARI SET FACTURAT = 1, ID_UTILFACT = ...
|
||||
WHERE ID_VANZARE IN (SELECT id_vanzare_aviz ... WHERE id_vanzare_Fact = pack_facturare.nid_vanzare AND sters=0 AND tip<>3)
|
||||
AND FACTURAT = 0;
|
||||
ELSE
|
||||
UPDATE VANZARI SET FACTURAT = 1, ...
|
||||
WHERE ID_VANZARE IN (
|
||||
SELECT id_vanzare FROM (
|
||||
SELECT a.id_vanzare, SUM(a.cantitate - NVL(b.cantitate,0)) AS ramas
|
||||
FROM vanzari_detalii a LEFT JOIN (...VANZARI_CANTITATI...) b ON ...
|
||||
WHERE a.id_vanzare IN (...avizele referite de aceasta factura...)
|
||||
GROUP BY a.id_vanzare
|
||||
) WHERE ramas = 0
|
||||
);
|
||||
END IF;
|
||||
```
|
||||
|
||||
**Aici exista un flag real persistat: `VANZARI.FACTURAT`.** `V_VERIFICARE` (= `pnParametruAditional`
|
||||
trimis de VFP) alege intre doua moduri:
|
||||
- `V_VERIFICARE = 0` (VFP a vazut `lnCantitateRamasa = 0`, adica a crezut ca tot ce era in avizele
|
||||
referite s-a facturat): **marcheaza TOATE avizele referite ca facturate, fara re-verificare** —
|
||||
increde in calculul VFP.
|
||||
- `V_VERIFICARE = 1` (VFP a vazut ceva ramas): **recalculeaza per-aviz, din tabele reale**
|
||||
(`vanzari_detalii`/`vanzari_cantitati`), si marcheaza **doar** avizele individuale al caror
|
||||
`ramas = 0` — mai stric, pentru ca VFP nu mai e sigur.
|
||||
|
||||
**Observatie relevanta pentru punctul 2 al plafonului de risc**: cand un singur `crsarticole` agrega
|
||||
mai multe avize (facturare din mai multe avize deodata), suma globala poate fi 0 chiar daca un aviz
|
||||
individual din grup mai are ramas si altul are exces care-l compenseaza — caz in care `V_VERIFICARE=0`
|
||||
ar marca **toate** ca facturate, inclusiv cel cu ramas real. **Comportament preexistent**, nu introdus
|
||||
de decuplare — dar varianta noua trebuie sa-l reproduca identic (aceeasi conditie de trigger: suma
|
||||
globala peste toate liniile din tranzactia curenta, nu per-document), nu sa-l "repare" din greseala.
|
||||
|
||||
### 3.4 Cine altcineva verifica remainder — `scrie_cantitati_vanzari_avize` (`:15420-15479`)
|
||||
|
||||
Ruleaza **necondiționat** pe ramura `tip=4`, indiferent de `V_VERIFICARE` — scrie in `VANZARI_CANTITATI`
|
||||
cate din cantitatile avizate au fost efectiv consumate de aceasta factura (mecanismul care alimenteaza
|
||||
`ramas` la sectiunea 3.3). Nu depinde de `crsarticole` — foloseste `VANZARI_DETALII_TEMP` (deja
|
||||
populat de `do_scrie_articole()`, apelat inaintea acestui `Do Case` din VFP, `:14264`) si tabele reale.
|
||||
**Confirma independenta totala de `crsarticole` a partii Oracle** — singurul lucru pe care Oracle il
|
||||
primeste de la VFP e semnalul binar `pnParametruAditional`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Proiectare — variante comparate
|
||||
|
||||
### Varianta (a) — cursor propriu, minimal, decuplat de populare
|
||||
|
||||
Un cursor separat (`crsregistru`), incarcat cu `id_c`/`id_articol` + cantitate ramasa, populat din
|
||||
aceeasi interogare care alimenteaza azi `crsarticole`, dar **independent** de campurile de populare
|
||||
(pret, TVA, valuta...) pe care S4 punctul 1 le muta pe cautare filtrata.
|
||||
|
||||
**Ce se atinge:** aceleasi patru metode (`do_adauga_articol`, `do_sterge`, `do_modifica`,
|
||||
`do_scrie_factura`) — schimba doar numele cursorului tinta, logica ramane identica.
|
||||
**Ce se rupe:** nimic structural — e schimbarea minima de sintaxa.
|
||||
**Cost:** mic, dar **nu rezolva problema de fond**: tot exista o a doua copie a "cat a mai ramas",
|
||||
tinuta manual in memorie VFP, care trebuie sa ramana in sincron cu ce calculeaza Oracle la scriere
|
||||
(sectiunea 3). E acelasi risc de azi, doar cu un cursor mai ingust. **Nu e "sursa unica"** — e aceeasi
|
||||
sursa dubla, doar mai mica.
|
||||
|
||||
### Varianta (b) — recalculul pe server la scriere, in loc de suma locala (RECOMANDATA pentru Rolul A)
|
||||
|
||||
Inlocuieste cele doua `Select crsarticole / Calculate Sum(cantitate) To lnCantitateRamasa` din
|
||||
`do_scrie_factura` (`:14303-14304`, `:14334-14335`) cu un apel Oracle nou, facut **dupa**
|
||||
`Thisform.do_scrie_articole()` (`:14264`, care populeaza deja `VANZARI_DETALII_TEMP` cu exact ce e pe
|
||||
cale sa fie scris) si **inainte** de `Do Case` care alege `scrie_factura_avize`/`scrie_factura2`.
|
||||
|
||||
Doua proceduri Oracle noi, minimale, care **reutilizeaza exact interogarea** pe care
|
||||
`inchide_comanda`/`marcheaza_facturat` o ruleaza oricum peste doua randuri mai jos in acelasi flux —
|
||||
nu o interogare noua, o extragere a celei existente intr-o forma care returneaza un numar in loc sa
|
||||
scrie:
|
||||
|
||||
```sql
|
||||
-- comanda: acelasi WHERE ca inchide_comanda (:5771-5818), dar SUM in loc de INSERT
|
||||
FUNCTION cantitate_ramasa_comanda(V_ID_COMANDA IN NUMBER) RETURN NUMBER IS
|
||||
V_RAMAS NUMBER;
|
||||
BEGIN
|
||||
SELECT NVL(SUM(SIGN(A.CANTITATE) *
|
||||
GREATEST(SIGN(A.CANTITATE) * (A.CANTITATE - NVL(B.CANTITATE,0) - NVL(C.CANTITATE,0)), 0)), 0)
|
||||
INTO V_RAMAS
|
||||
FROM COMENZI_ELEMENTE A
|
||||
LEFT JOIN (SELECT ID_ARTICOL, ID_POL, ID_VALUTA, PRET, SUM(CANTITATE) AS CANTITATE
|
||||
FROM VANZARI_DETALII_TEMP GROUP BY ID_ARTICOL, ID_POL, ID_VALUTA, PRET) B
|
||||
ON A.ID_ARTICOL = B.ID_ARTICOL AND A.ID_POL = B.ID_POL AND A.ID_VALUTA = B.ID_VALUTA AND A.PRET = B.PRET
|
||||
LEFT JOIN (... acelasi C ca in inchide_comanda, VANZARI/VANZARI_DETALII istoric ...) C ON ...
|
||||
WHERE A.STERS = 0 AND A.ID_COMANDA = V_ID_COMANDA;
|
||||
RETURN V_RAMAS;
|
||||
END;
|
||||
|
||||
-- avize: acelasi calcul ca in marcheaza_facturat(V_VERIFICARE=1), dar returnat, nu aplicat
|
||||
FUNCTION exista_ramas_avize(V_LISTAID IN VARCHAR2) RETURN NUMBER IS ...
|
||||
```
|
||||
|
||||
**Ce se atinge:** `do_scrie_factura` (inlocuieste 4 linii VFP cu un apel Oracle + citire scalar), plus
|
||||
doua functii Oracle noi in `PACK_FACTURARE`, care **extrag** logica deja scrisa in `inchide_comanda`/
|
||||
`marcheaza_facturat`, nu o duplica din nimic. `do_adauga_tot`, `do_sterge`, `do_modifica` **raman
|
||||
neschimbate pentru Rolul A** — nu mai au ce sa faca, pentru ca nimic nu mai citeste suma lor.
|
||||
**Ce se rupe:** niciun comportament — recalculul Oracle e **mai precis** decat suma locala (vede
|
||||
concurenta: doi operatori facturand din aceeasi comanda simultan; `crsarticole` local nu vede
|
||||
modificari facute de alt utilizator dupa deschiderea formularului — bug latent de azi, pe care
|
||||
varianta (b) il elimina ca efect secundar, nu ca scop).
|
||||
**Cost:** doua functii Oracle noi (SQL, nu logica noua — extrase din proceduri existente), un apel
|
||||
suplimentar `goExecutor` in `do_scrie_factura`.
|
||||
**De ce e "sursa unica":** Oracle calculeaza "cat a ramas" o singura data, in doua locuri (recalcul
|
||||
inainte de scriere + recalcul in `inchide_comanda`/`marcheaza_facturat` la scriere), din **aceeasi
|
||||
interogare**, niciodata dintr-o copie VFP. Nu mai exista sincronizare de mentinut, pentru ca nu mai
|
||||
exista a doua stare.
|
||||
|
||||
### Varianta (c) — plafon Rol B recalculat la cerere, nu tinut in memorie (RECOMANDATA pentru Rolul B)
|
||||
|
||||
Pentru grupul Rol B (`1,22,29,2`-lista, `23,41`, `8,9,24`), plafonul din `do_modifica`
|
||||
(`lnCantitateMax`, sectiunea 1.3(a)) si decrementul din `do_adauga_articol`/`do_sterge` (sectiunea
|
||||
1.1/1.2) devin un apel Oracle facut **la fiecare adaugare/editare**, in loc de o valoare tinuta in
|
||||
`crsarticole` — interogare filtrata pe `id_articol`, aceeasi forma ca `cursor_preturi`/
|
||||
`cursor_gestiune` filtrate (deja proiectate la S4 punctul 1, sectiunea 4), minus stocul deja rezervat
|
||||
in sesiunea curenta (calculabil din `crsfactura` insusi, sumand liniile deja adaugate cu acelasi
|
||||
`id_articol` — cursor local, dar de linii **efectiv adaugate**, nu de "cat mai era", deci nu mai poate
|
||||
diverge de continutul real al facturii in curs).
|
||||
|
||||
**Ce se atinge:** `do_adauga_articol` (:13034-13051 dispare, inlocuit cu un apel la cerere din
|
||||
`do_alege_stoc`, care oricum face verificare de stoc pe server — de confirmat la implementare daca
|
||||
verificarea existenta acopera deja acest plafon sau trebuie extinsa), `do_sterge` (:14640-14669
|
||||
Rol B dispare, ramane doar Rolul A), `do_modifica` (:13775-13798 plafonul se cere pe server, nu se
|
||||
reconstituie din cursor).
|
||||
**Ce se rupe:** **de verificat cu Marius** — plafonul Rol B azi e un avertisment soft (`do_alege_stoc`
|
||||
primeste `lnCantitateMax` ca parametru, dar verificarea reala de stoc la comitere ramane oricum in
|
||||
Oracle, la `adauga_articol_factura`/scriere; codul citit aici nu arata un blocaj hard bazat pe
|
||||
`crsarticole` — de confirmat explicit, nu presupus, ca UX-ul de azi (mesaj/limitare la alegerea
|
||||
cantitatii) nu depinde de faptul ca plafonul persista *in memorie* intre doua adaugari ale *aceluiasi*
|
||||
articol in aceeasi sesiune, ci doar de valoarea citita la momentul respectiv).
|
||||
**Cost:** mediu — mai multe interogari Oracle mici (una per adaugare/editare de linie gestionabila),
|
||||
in loc de aritmetica locala. Justificat de acelasi motiv ca varianta (b): elimina a doua copie.
|
||||
|
||||
### Comparatie si recomandare finala
|
||||
|
||||
| | (a) cursor propriu minimal | (b) recalcul server (Rol A) | (c) plafon la cerere (Rol B) |
|
||||
|---|---|---|---|
|
||||
| E "sursa unica"? | Nu — copie mai mica, tot manuala | **Da** | **Da** |
|
||||
| Risc de divergenta fata de Oracle | Identic cu azi | Eliminat (Oracle citeste Oracle) | Eliminat |
|
||||
| Cost implementare | Mic | Mic-mediu (2 functii Oracle) | Mediu (mai multe roundtrip-uri) |
|
||||
| Rezolva corectia sectiunii 0 (23/41 blocheaza punctul 1) | Nu direct | N/A (23/41 nu sunt Rol A) | **Da** |
|
||||
|
||||
**Recomandare: (b) pentru Rolul A, (c) pentru Rolul B.** Impreuna elimina complet nevoia de a incarca
|
||||
`crsarticole` in masa pentru bookkeeping — incarcarea ramasa (daca ramane) e strict pentru
|
||||
**enumerare** la "adauga tot" (sectiunea 1.4), care e un scop diferit si mai ingust (nu mai trebuie sa
|
||||
tina sincron o cantitate, doar sa listeze randuri candidate o singura data, la deschidere).
|
||||
|
||||
---
|
||||
|
||||
## 5. Cazul limita: adauga si sterge inainte de salvare
|
||||
|
||||
**Azi:** `do_adauga_articol` decrementeaza `crsarticole`/`crsarticole1` (Rol A si/sau B, dupa tip);
|
||||
`do_sterge` reface exact simetric, inainte de orice salvare (sectiunile 1.1-1.2 sunt perechi
|
||||
simetrice pe fiecare grup de tipuri). Suma finala din `crsarticole` la momentul `do_scrie_factura`
|
||||
reflecta corect doar liniile **ramase** in `crsfactura`, indiferent cate au fost adaugate si sterse
|
||||
intre timp — pentru ca fiecare stergere anuleaza exact adaugarea corespunzatoare.
|
||||
|
||||
**In varianta recomandata (b+c):** cazul limita devine **trivial**, nu doar acoperit — pentru ca nu
|
||||
mai exista o stare intermediara de sincronizat. Rolul A: `do_scrie_factura` calculeaza remainder-ul
|
||||
o singura data, la scriere, din `VANZARI_DETALII_TEMP` care contine **exact** liniile ramase in
|
||||
`crsfactura` la acel moment (populat de `do_scrie_articole()` chiar inainte, `Scan` peste `crsfactura`
|
||||
curent — sectiunea 4 varianta (b)) — liniile adaugate-si-sterse nu ajung niciodata in
|
||||
`VANZARI_DETALII_TEMP`, deci nu influenteaza calculul, fara nicio actiune suplimentara. Rolul B:
|
||||
plafonul la fiecare adaugare se cere din nou pe server, minus ce e deja in `crsfactura` in acel
|
||||
moment — o stergere anterioara pur si simplu nu mai apare in acea suma, fara "refacere" explicita.
|
||||
**Cazul limita nu mai e un caz special de tratat — e comportamentul implicit al oricarei citiri facute
|
||||
din starea curenta, in loc de dintr-un contor tinut manual.**
|
||||
|
||||
---
|
||||
|
||||
## 6. Per tip de document
|
||||
|
||||
| Tip(uri) | Rol azi | Ce se schimba in varianta recomandata |
|
||||
|---|---|---|
|
||||
| **Comanda** `3,21,25,28,42,47` | Rol A (inchidere automata) | `do_scrie_factura` cheama functia Oracle noua (4b) in loc de `Calculate Sum`; `do_adauga_tot`/`do_sterge` raman (enumerare + Rol B nu se aplica pe comanda insasi, doar pe articolele ei individuale daca sunt gestionabile — **de verificat**, comanda nu apare in niciuna din listele Rol B de la sectiunea 1, deci pare curatata deja) |
|
||||
| **Avize** `4` | Rol A (marcheaza_facturat) | idem, functia Oracle pentru avize (4b) |
|
||||
| **Contract** `2,6,26,52` | Jumatate `crsarticole` (delegat la `cursor_preturi`, per raportul punctului 1) e Rol B doar pentru `poDate.tip=2 And opt_facturare=0`; restul (`crsarticole1`, rate+articole `OPT_FACTURARE=3`) nu are bookkeeping in sectiunea 1 (needitat de cautare, ramane cum e azi, per raportul punctului 1 sectiunea 7) | Rolul B pe jumatatea `opt_facturare=0` trece pe varianta (c); `26,52` nu apar in niciuna din listele Rol B/A gasite in cod — **de verificat separat, posibil fara bookkeeping deloc pe aceste doua**, nesemnalat ca atare in codul citit aici |
|
||||
| **Transfer** `41` (standard); `23` (doar prototip, standard il trateaza ca lista de preturi) | Rol B pur (niciun Rol A — confirmat sectiunea 3.1, `41`/`23` cad pe `ELSE` in `finalizeaza_factura`) | Trece integral pe varianta (c); **aceasta e corectia care debloca pasul 5 al punctului 1** — fara ea, oprirea incarcarii in masa pe `23,41` (propusa acolo) ar sparge plafonul de stoc existent |
|
||||
| **Lista de preturi** `1,5,7,10,22,29` | Rol B doar pentru articole gestionabile (`1,22,29`; `5,7,10` nu apar in Do Case-urile Rol B — **fara bookkeeping**, confirmat pe cod) | `1,22,29` trec pe varianta (c); `5,7,10` nu au nimic de decuplat, deja curatate |
|
||||
| **Retur** `8,9,24` | Rol B (inversul directiei fata de restul) | Varianta (c), simetric |
|
||||
| **Restaurant `45`, K `48,49`** | Nu ating Rol A/B (in afara `Do Case`-urilor gasite; `45` explicit exclus la `poArticol.gestionabil=0 Or gnScadereStoc=0 Or poDate.tip=45` in ambele metode, deci ocoleste bookkeeping-ul indiferent de gestionabilitate) | Fara schimbare — deja fara bookkeeping de decuplat |
|
||||
|
||||
**Tipuri semnalate ca neclare, de stabilit separat, nu presupuse aici:** `26` (aviz din contract) si
|
||||
`52` (contract in valuta/alt subtip) nu apar explicit in niciuna din listele Rol A/B din sectiunea 1 —
|
||||
codul citit in aceasta sesiune nu confirma nici prezenta, nici absenta bookkeeping-ului pe ele
|
||||
specific; tratamentul lor pare sa urmeze grupul `2,6` din `Inlist`-urile care le includ, dar niciun
|
||||
`Do Case` din sectiunea 1 nu le mentioneaza individual in afara de includerea in `Inlist(poDate.tip,
|
||||
2, 6, 52)` la `do_scrie_articole` (rate) — nu la bookkeeping-ul de `crsarticole`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Pasi de implementare, ordonati
|
||||
|
||||
1. **Pas 1 — Oracle: extrage functiile de recalcul remainder** (`cantitate_ramasa_comanda`,
|
||||
`exista_ramas_avize`) din logica deja scrisa in `inchide_comanda`/`marcheaza_facturat`, fara sa
|
||||
modifice acele proceduri. *Gata cand:* apelate manual cu parametrii unei comenzi/unui grup de avize
|
||||
cunoscute, valoarea returnata coincide cu suma pe care `Calculate Sum(cantitate)` din VFP o
|
||||
calculeaza azi peste `crsarticole`, pe acelasi document, in aceeasi stare (comparatie directa,
|
||||
inainte de orice alta schimbare).
|
||||
2. **Pas 2 — VFP: `do_scrie_factura` cheama functiile noi in loc de `Calculate Sum`** (sectiunea 4b),
|
||||
pastrand identic restul logicii (`pnFacturaRetur`, mesajele de confirmare, `pnParametruAditional`).
|
||||
*Gata cand:* pe un document de comanda si unul de aviz, cu acelasi scenariu (facturare partiala),
|
||||
dialogul de confirmare apare in acelasi moment si cu acelasi rezultat ca inainte de Pas 2 — regresie
|
||||
manuala, comparatie inainte/dupa pe acelasi document.
|
||||
3. **Pas 3 — Oracle: functie de plafon Rol B filtrata pe articol**, reutilizand `WHERE`-ul din
|
||||
`cursor_preturi`/`cursor_gestiune` (deja proiectat la S4 punctul 1, sectiunea 4), minus ce e deja in
|
||||
`crsfactura` pentru acelasi articol. *Gata cand:* pe un articol gestionabil cunoscut, valoarea
|
||||
returnata coincide cu `crsarticole.cantitate` de azi, la aceeasi stare a sesiunii (inainte de orice
|
||||
adaugare).
|
||||
4. **Pas 4 — VFP: `do_adauga_articol`/`do_sterge`/`do_modifica` folosesc plafonul cerut pe server**
|
||||
pentru grupul Rol B, in loc de `Replace cantitate` pe `crsarticole` (sectiunea 4c). *Gata cand:*
|
||||
cazul limita de la sectiunea 5 (adauga-apoi-sterge inainte de salvare) produce acelasi plafon
|
||||
disponibil ca azi, verificat manual pe un articol cu stoc limitat.
|
||||
5. **Pas 5 — activarea pasului 5 al punctului 1 pe `23`/`41`** (oprirea incarcarii in masa), acum ca
|
||||
Pasul 4 a mutat Rolul B in afara lui `crsarticole`. *Gata cand:* deschiderea formularului pe tip
|
||||
`41` (si `23` pe prototip) nu mai executa `cursor_gestiune`/`cursor_preturi` la deschidere, dar
|
||||
adaugarea unui articol tot respecta plafonul de stoc (verificat manual).
|
||||
6. **Pas 6 — curatare: `crsarticole` ramane incarcat doar unde e nevoie de enumerare** (comanda, aviz,
|
||||
contract-rate — pentru "adauga tot" existent), fara bookkeeping legat de el. *Gata cand:* grep pe
|
||||
`ofacturare.vc2` pentru `Replace cantitate.*crsarticole` (in afara populare) nu mai gaseste
|
||||
potriviri in `do_adauga_articol`/`do_sterge`/`do_modifica`.
|
||||
7. **Pas 7 — verificare paritate finala** (sectiunea 8), pe toate tipurile cu document sursa.
|
||||
|
||||
*Depinde de:* S4 punctul 1 (Pasii 1-4 din PROIECTAREA acelui punct trebuie sa existe, dar Pasii 1-4
|
||||
**de aici** pot fi facuti independent, inaintea sau in paralel cu populare — nu depind de cautarea
|
||||
filtrata, doar de `crsarticole`/`crsfactura` existente azi). Pasul 5 de aici depinde explicit de Pasul
|
||||
4 de aici (nu poate porni inaintea lui).
|
||||
|
||||
---
|
||||
|
||||
## 8. Cum se verifica paritatea inchiderii automate
|
||||
|
||||
**Comanda** (nu exista flag `INCHISA` persistat — cautat explicit in tot pachetul, zero potriviri;
|
||||
starea se deriva din `COMENZI_ELEMENTE` vs `VANZARI_DETALII`):
|
||||
|
||||
```sql
|
||||
-- inainte de facturare partiala + inchidere fortata (flux vechi vs nou, aceeasi comanda X)
|
||||
{call pack_facturare.cursor_comanda(V_DATA_CURS, V_TIP, 'X', V_ID_UTIL, :cursor)}
|
||||
-- numara randurile ramase (trebuie sa fie 0 dupa inchidere fortata, identic pe ambele fluxuri)
|
||||
SELECT COUNT(*) FROM (...continutul cursorului de mai sus...);
|
||||
|
||||
-- verificare directa a compensatiei inserate de inchide_comanda
|
||||
SELECT ID_ARTICOL, ID_POL, CANTITATE FROM COMENZI_ELEMENTE
|
||||
WHERE ID_COMANDA = :X ORDER BY ID_COMANDA_ELEMENT DESC; -- randul nou trebuie sa fie identic pe ambele fluxuri (aceeasi cantitate compensatorie)
|
||||
```
|
||||
|
||||
**Avize** (flag real: `VANZARI.FACTURAT`):
|
||||
|
||||
```sql
|
||||
SELECT ID_VANZARE, FACTURAT, ID_UTILFACT FROM VANZARI
|
||||
WHERE ID_VANZARE IN (:lista_avize_test)
|
||||
ORDER BY ID_VANZARE;
|
||||
-- rulat dupa fiecare flux (vechi, apoi nou, pe date de test resetate identic), FACTURAT trebuie sa coincida rand cu rand
|
||||
```
|
||||
|
||||
**Protocol de comparatie**, aplicabil pe fiecare tip cu document sursa (comanda: alege una din
|
||||
`3,21,25,28,42,47`; avize: `4`):
|
||||
1. reseteaza datele de test la aceeasi stare (aceeasi comanda/aviz, aceleasi cantitati ramase);
|
||||
2. factureaza partial pe fluxul **vechi** (cod actual), noteaza rezultatul interogarilor de mai sus;
|
||||
3. reseteaza din nou la aceeasi stare initiala;
|
||||
4. factureaza identic (aceleasi linii, aceleasi cantitati) pe fluxul **nou** (dupa Pasii 1-2 din
|
||||
sectiunea 7), noteaza acelasi rezultat;
|
||||
5. compara — trebuie sa fie identic, inclusiv pe cazul limita de la sectiunea 5 (adauga-si-sterge
|
||||
inainte de salvare) si pe cazul avizelor multiple agregate intr-o singura factura (sectiunea 3.3,
|
||||
riscul de marcare in bloc).
|
||||
|
||||
**Zero cazuri testate azi nu e dovada** (memorie de proiect) — protocolul de mai sus cere minim un
|
||||
caz per tip din lista, plus cazul limita, nu doar "a mers o data".
|
||||
|
||||
---
|
||||
|
||||
## 9. Riscuri si ce ramane de decis de Marius
|
||||
|
||||
- **Corectia sectiunii 0/6**: pasul 5 al proiectarii punctului 1 (`s4_cautare_articole_server.md:417-424`)
|
||||
presupune ca `23,41` n-au bookkeeping de pastrat — gasit aici ca au Rol B activ. **De confirmat cu
|
||||
Marius daca punctul 1 se re-deschide pentru aceasta corectie sau ramane cum e, cu mentiunea ca
|
||||
aplicarea pe `23,41` asteapta finalizarea punctului 2** (asa cum recomand la Pasul 5, sectiunea 7).
|
||||
- **Asimetria `do_modifica` fata de `do_adauga_articol`/`do_sterge`** (sectiunea 1.3, grupul
|
||||
`1,22,29,2`-lista nu se ajusteaza la editarea cantitatii unei linii existente) — **preexistenta**,
|
||||
nu introdusa de decuplare. De decis daca varianta noua (Rol B pe server, sectiunea 4c) trebuie sa
|
||||
reproduca exact aceasta asimetrie (plafonul nu se recalculeaza corect la editare pe aceste tipuri,
|
||||
ca azi) sau sa o corecteze ca efect secundar al recalcularii la cerere (care, prin natura ei, ar
|
||||
elimina automat asimetria — orice cerere de plafon citeste starea curenta, indiferent daca a fost
|
||||
o adaugare sau o editare). **Recomandare: lasat sa se corecteze de la sine** (comportament mai
|
||||
corect, cost zero suplimentar, dar semnaleaza explicit ca S4 schimba acest comportament punctual,
|
||||
nu doar "decupleaza" — de mentionat in changelog daca se alege aceasta cale).
|
||||
- **Riscul de concurenta multi-utilizator, semnalat ca beneficiu la sectiunea 4b, dar nediscutat cu
|
||||
Marius**: varianta recomandata face `crsarticole` local sa nu mai poata diverge de Oracle *la
|
||||
scriere*, dar tot poate divarge *in timpul editarii* (doi operatori pe aceeasi comanda, unul
|
||||
vede plafonul invechit pana la urmatoarea lui adaugare/editare). Nu e un risc nou introdus — e
|
||||
identic cu azi pe cursorul static incarcat o data la deschidere — dar varianta (c) il reduce (cere
|
||||
plafonul proaspat la fiecare adaugare, nu o singura data la deschidere), fara sa-l elimine complet
|
||||
(ramane fereastra intre "am cerut plafonul" si "am scris linia"). De mentionat ca imbunatatire, nu
|
||||
garantie.
|
||||
- **Cont Rol B pe contract `26,52`**: nu s-a gasit dovada nici de prezenta, nici de absenta bookkeeping
|
||||
pe aceste doua tipuri in codul citit (sectiunea 6) — de verificat separat inainte de a le include in
|
||||
Pasul 3/4 al implementarii, nu de presupus ca urmeaza grupul `2,6`.
|
||||
- **Functiile Oracle noi (Pas 1, sectiunea 7) sunt o extragere, nu o duplicare** — dar tot inseamna cod
|
||||
PL/SQL nou in `PACK_FACTURARE`, care trebuie revizuit separat de cineva familiar cu pachetul
|
||||
(schema exacta a `JOIN`-urilor din `inchide_comanda`, reprodusa aici din citire, nu din executie
|
||||
reala pe Oracle — verificarea Pasului 1 din sectiunea 7 e obligatorie inainte de a continua).
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare incheiata in aceasta sesiune. Toate cele 9 puncte cerute sunt acoperite, cu citate
|
||||
`fisier:linie` verificate direct pe fisierele reale (`ofacturare.vc2`, nu `.bak`; corpul
|
||||
`PACK_FACTURARE.sql`, nu spec-ul comentat). Descoperirea centrala (Rol A vs Rol B, si corectia asupra
|
||||
pasului 5 al punctului 1 pentru `23`/`41`) nu era vizibila din raportul punctului 1 — acela trata
|
||||
`crsarticole` ca un singur registru omogen; aici s-a aratat ca sunt doua mecanisme cu scopuri
|
||||
diferite, unul (Rol A) recalculat oricum de Oracle la scriere si deci usor de mutat pe server fara
|
||||
pierdere de paritate, celalalt (Rol B) un plafon UI care nu alimenteaza nicio decizie de business.
|
||||
|
||||
Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, nicio scriere pe Oracle (doar
|
||||
`Read`/`Select-String`/`grep` pe fisiere de pe disc).
|
||||
246
docs/cercetare/s4_puncte_deschise.md
Normal file
246
docs/cercetare/s4_puncte_deschise.md
Normal file
@@ -0,0 +1,246 @@
|
||||
# Cercetare — doua puncte deschise inainte de implementarea S4
|
||||
|
||||
Investigatie READ-ONLY, doua intrebari inchise din `docs\plan_13_unificare_formular_facturare.md`,
|
||||
`#### S4`, sectiunea "De inchis inainte de implementare" (`:2092-2095`). Fara editari de cod, fara
|
||||
`git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara scriere pe Oracle (numai `SELECT`).
|
||||
|
||||
Status: **GATA**. Ambele intrebari au raspuns confirmat pe cod, cu `fisier:linie`. Amandoua
|
||||
raspunsurile de baza confirma "nu schimba nimic", dar fiecare are o rezerva concreta, nu triviala,
|
||||
de pus in proiectarea S4 (nu doar de bifat).
|
||||
|
||||
Sursa Oracle: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(17217 linii — liniile citate mai jos sunt verificate direct pe acest fisier, corpul pachetului).
|
||||
Sursa VFP: `COMUN\clase\ofacturare.vc2`, `COMUN\programe\ofacturare.prg` (perimetrul `frm_facturare_articole`
|
||||
/ `factureaza`; `frm_facturare_articole2` / `factureaza2` citite doar pentru comparatie, nu atinse).
|
||||
|
||||
---
|
||||
|
||||
## Intrebarea 1 — `id_jtva_coloana` lipseste din `cursor_preturi`?
|
||||
|
||||
### 1.1 Ce e si cine o consuma
|
||||
|
||||
`id_jtva_coloana` identifica randul din `JTVA_COLOANE` (explicatia/coloana de TVA folosita la
|
||||
raportare si SAFT) asociat unei linii de factura. E scris pe fiecare linie in `VANZARI_DETALII_TEMP`
|
||||
(Oracle, prin `adauga_articol_factura`) si citit inapoi de UI-ul VFP pentru dialogul de "explicatie
|
||||
TVA" (`Cb_explicatie_Tva`, `frm_articol_factura`).
|
||||
|
||||
### 1.2 Chiar lipseste din `cursor_preturi`? — confirmat pe SQL
|
||||
|
||||
Toate cele **cinci ramuri** ale `cursor_preturi` (`WHEN V_TIP = 45`, `WHEN V_TIP IN (1,2)`,
|
||||
`WHEN V_TIP IN (5,6,10,52)`, `WHEN V_TIP = 7`, `ELSE` aviz — corpul la
|
||||
`ff_...PACK_FACTURARE.sql:2138-2644`) au liste de coloane complete verificate direct — **niciuna nu
|
||||
returneaza `id_jtva_coloana`** (`:2163-2221`, `:2268-2329`, `:2394-2427`, `:2470-2504`, `:2545-2603`).
|
||||
`cursor_facturare` e un `REF CURSOR` slab tipat (`:28`), deci lista de coloane a fiecarei ramuri e
|
||||
literal ce se vede in `SELECT`, fara camp implicit.
|
||||
|
||||
Acelasi lucru e adevarat si pentru **`cursor_gestiune`** (`:4158-4347`, coloanele la `:4207-4343`) —
|
||||
relevant pentru intrebarea 2, tipul 41. `cursor_contract` (`:2646-2950`) delega `V_CURSOR` la
|
||||
`cursor_preturi` (deci mosteneste lipsa), iar `V_CURSOR2` (rate + articole `OPT_FACTURARE=3`,
|
||||
`:2718-2938`) **de asemenea nu are `id_jtva_coloana`** in niciuna din cele doua ramuri `UNION ALL`.
|
||||
|
||||
Prin contrast, **`cursor_avize`** (`:3703-3874`) **are** `A.ID_JTVA_COLOANA` explicit (`:3748`,
|
||||
plus `GROUP BY`-urile de la `:3781`, `:3804`, `:3822`, `:3843`) si `cursor_comanda` (`:2952-3171`)
|
||||
**nu are**, pe niciuna din cele doua ramuri (factura/aviz, `:2994-3170`). Deci impartirea reala e:
|
||||
*are* — doar `cursor_avize`; *nu are* — `cursor_preturi`, `cursor_gestiune`, `cursor_contract`
|
||||
(ambele cursoare), `cursor_comanda`.
|
||||
|
||||
### 1.3 E completat in alt pas, sau ramane gol? — DA, e completat, printr-un mecanism in doi pasi
|
||||
|
||||
Pasul 1 — **valoare implicita, nu `NULL`**. `do_initializeaza_articol`
|
||||
(`ofacturare.vc2:13681-13683`, identic la `:17896-17897` pe prototip):
|
||||
```
|
||||
If Type('toArticol.id_jtva_coloana') = 'U'
|
||||
AddProperty(toArticol,'id_jtva_coloana',0)
|
||||
Endif
|
||||
```
|
||||
Daca `Scatter Name poArticol` dintr-un cursor fara coloana `id_jtva_coloana` produce un obiect fara
|
||||
acea proprietate (`Type(...) = 'U'`), se adauga cu valoarea **`0`** (numeric, nu `NULL`).
|
||||
|
||||
Pasul 2 — **derivare reala din `proc_tvav`, neconditionata**. `frm_articol_factura.Init`
|
||||
(`ofacturare.vc2:2344-2371`) suprascrie *intotdeauna* `id_jtva_coloana`, indiferent de ce a venit din
|
||||
cursor:
|
||||
```
|
||||
Select jtva_coloane
|
||||
If !Isnull(poArticol.proc_tvav)
|
||||
Set Filter To cota_tva = poArticol.proc_tvav * 100 - 100
|
||||
...
|
||||
poArticol.id_jtva_coloana = id_jtva_coloana
|
||||
Else
|
||||
...
|
||||
poArticol.proc_tvav = (cota_tva + 100)/100
|
||||
poArticol.id_jtva_coloana = id_jtva_coloana
|
||||
EndIf
|
||||
```
|
||||
`proc_tvav` **este** returnat de toate cele cinci ramuri ale `cursor_preturi` (coloana comuna,
|
||||
confirmata la 1.2), deci lookup-ul are mereu ce derivea, indiferent daca `id_jtva_coloana` a lipsit
|
||||
din cursorul sursa.
|
||||
|
||||
Acest `Init` ruleaza pentru **orice** linie adaugata prin `do_adauga_articol`
|
||||
(`ofacturare.vc2:12813-13167`), pe ambele ramuri ale `Do Case` de la `:12870-12896`:
|
||||
- articol negestionabil / `gnScadereStoc=0` / restaurant (`tip=45`) →
|
||||
`Createobject("frm_articol_factura", ...)` direct (`:12873`);
|
||||
- articol gestionabil (`Otherwise`, `:12881-12895`) → `do_alege_stoc` →
|
||||
`Createobject("frm_articol_gest_factura", ...)` (`:13353`/`:13361`/`:13375`) — clasa care
|
||||
**mosteneste** `frm_articol_factura` (`vfp_symbols -Class`: `frm_articol_gest_factura ->
|
||||
frm_articol_factura -> _frmbase -> _form`) si al carei `Init` (`:3986-3989`) cheama
|
||||
`DoDefault(tnCantitate,tlAscunde)` — deci ruleaza acelasi `Init` de la 2315-2371, cu aceeasi
|
||||
derivare.
|
||||
|
||||
**Confirmare ca derivarea chiar functioneaza si nu doar exista in cod:** `do_alege_stoc`
|
||||
(`:13330-13340`) avea, intr-o versiune anterioara (comentata, `v 2.2.18`), un scurtcircuit care
|
||||
seta direct `id_jtva_coloana` fara sa mai arate formularul cand exista o singura optiune de TVA;
|
||||
azi acel scurtcircuit e dezactivat si se creeaza intotdeauna obiectul `frm_articol_gest_factura`
|
||||
(deci `Init` ruleaza mereu, nu doar in cazuri rare).
|
||||
|
||||
Safety-net-ul `Case Empty(poArticol.id_jtva_coloana)` din `inainte_de_do_termin`
|
||||
(`:2205-2208`, mostenit si de `frm_articol_gest_factura`) e o verificare reziduala pentru cazul in
|
||||
care nici `proc_tvav` n-are match in `jtva_coloane` — nu dovada ca `id_jtva_coloana` ramane gol azi
|
||||
pe fluxul normal.
|
||||
|
||||
### 1.4 Concluzia care conteaza — cu o rezerva reala, nu ipotetica
|
||||
|
||||
**Pe fluxul de azi (`do_adauga_articol`), lipsa lui `id_jtva_coloana` din `cursor_preturi`/
|
||||
`cursor_gestiune` NU e un bug** — e completat corect, de doua ori (default 0, apoi derivat real din
|
||||
`proc_tvav`), pe ambele ramuri (gestionabil/negestionabil), inainte ca linia sa ajunga in
|
||||
`crsfactura` (`Gather ... id_jtva_coloana ...`, `:12945-12950`/`:12992-12995`) si, de acolo, in
|
||||
Oracle (`do_scrie_articole` → `adauga_articol_factura(...)`, `Alltrim(Str(poArt.id_jtva_coloana))`,
|
||||
`:14085`).
|
||||
|
||||
**Rezerva: raspunsul "filtrarea nu schimba nimic" e adevarat DOAR daca implementarea S4 pastreaza
|
||||
trecerea prin `do_adauga_articol`/`frm_articol_factura.Init` pentru linia aleasa.** Cercetarea
|
||||
anterioara (`docs\cercetare\s4_cautare_articole_server.md`, sectiunea 5) arata ca **singurul exemplu
|
||||
real existent de cautare-pe-server** (`combosql`, `grd_factura.cCodMat.cboCodmat.LostFocus`,
|
||||
`ofacturare.vc2:19288-19306`) **nu trece prin `do_adauga_articol` deloc** — face `REPLACE` direct in
|
||||
`crsFactura`:
|
||||
```
|
||||
REPLACE codmat WITH crsCodMat.codmat, denumire WITH crsCodmat.denumire, id_articol WITH crsCodmat.id_articol IN crsFactura
|
||||
```
|
||||
Acelasi tipar identic pe coloana `cDenumire` (`:19312-19317`). Daca S4 extinde acest `REPLACE` cu
|
||||
restul campurilor din cursorul filtrat — asa cum recomanda raportul anterior la sectiunea 5, punctul
|
||||
4 ("sa extinda REPLACE-ul... cu restul campurilor") — **`id_jtva_coloana` nu mai trece prin niciun
|
||||
`do_initializeaza_articol`/`frm_articol_factura.Init`**, pentru ca acel `REPLACE` nu creeaza obiectul
|
||||
`poArticol` si nu instantiaza formularul. Cursorul filtrat tot n-are `id_jtva_coloana` (varianta
|
||||
filtrata a `cursor_preturi` pastreaza aceeasi lista de coloane — vezi
|
||||
`s4_cautare_articole_server.md:220`), deci randul din `crsFactura` ar ramane cu valoarea implicita
|
||||
de camp (`Append Blank`, nu exista un `REPLACE` explicit pe acea coloana) — **fara nicio derivare din
|
||||
`proc_tvav`**.
|
||||
|
||||
La scriere, `adauga_articol_factura` (Oracle) cauta `ID_JTVA_COLOANA = V_ID_JTVA_COLOANA` in
|
||||
`JTVA_COLOANE` pe ramura `ELSE` a `CASE`-ului sau (`ff_...PACK_FACTURARE.sql:5187-5198` — ramura care
|
||||
se aplica exact tipurilor de lista de preturi 1/5/7/10/22/29, tipurile de transfer 23/45/48/49 si
|
||||
altele care nu intra in ramurile speciale comanda/aviz/restaurant/contract-cu-pret-fix) si, daca nu
|
||||
gaseste o potrivire, **arunca `RAISE_APPLICATION_ERROR(-20000, 'Nu a fost gasita cota de TVA!
|
||||
(FACT-013 : ...)')`**. O valoare implicita de camp (tipic `0`, posibil `NULL` dupa `Append Blank`,
|
||||
de verificat pe structura reala a `crsfactura`) ar produce fie acest -20000, fie (daca exista un rand
|
||||
`JTVA_COLOANE.ID_JTVA_COLOANA=0`) o cota de TVA gresita scrisa tacut — amandoua ar fi un **bug nou,
|
||||
introdus de S4**, nu unul preexistent.
|
||||
|
||||
**Recomandare concreta pentru proiectarea S4 (nu doar constatare):** varianta filtrata a cautarii
|
||||
(oricare ar fi mecanismul concret — `combosql` extins sau altceva) trebuie sa continue sa treaca prin
|
||||
`do_adauga_articol`/`frm_articol_factura.Init` pentru linia aleasa (sau sa reproduca explicit
|
||||
derivarea din `proc_tvav` daca se alege un `REPLACE` direct), nu doar sa extinda `REPLACE`-ul de azi
|
||||
cu campurile brute din cursor. De pus explicit ca cerinta in proiectarea S4, nu ca detaliu care se
|
||||
rezolva de la sine.
|
||||
|
||||
---
|
||||
|
||||
## Intrebarea 2 — `cursor_gestiune` (tipurile 23/41) fara buton "adauga tot"?
|
||||
|
||||
### 2.1 Vizibilitatea butonului pe cod — confirmata
|
||||
|
||||
`but_urmator_tot1` are **`Visible = .F.` la design-time**
|
||||
(`ofacturare.vc2:11257-11266`, `ADD OBJECT 'But_urmator_tot1' AS but_urmator_tot WITH ... Visible =
|
||||
.F. ...`). Devine vizibil doar prin `This.but_urmator_tot1.Visible = .T.` explicit, in 8 locuri din
|
||||
`Do Case poDate.tip` din `Init` (`:15108-15248`):
|
||||
|
||||
| Tip | Vizibil? | Linia care il seteaza |
|
||||
|---|---|---|
|
||||
| `eProforma=1` | da | `:15113` |
|
||||
| `lCopiere` | da | `:15120` |
|
||||
| `1,5,7,10` (lista de preturi) | **nu** (fara linie) | — |
|
||||
| `2,6` (contract) | **nu** | — |
|
||||
| `3` (comanda) | da | `:15150` |
|
||||
| `4` (avize) | da | `:15166` |
|
||||
| `21,28,42,47` (aviz din comanda) | da | `:15177` |
|
||||
| `22,29` (aviz din lista de preturi) | **nu** | — |
|
||||
| **`23`** (transfer subunitati) | **nu** — are propriul `Case` (`:15187-15195`), dar niciun `Visible=.T.` in el | — |
|
||||
| **`41`** (retur transfer) | **nu** — are propriul `Case` (`:15197-15205`), dar niciun `Visible=.T.` in el | — |
|
||||
| `25` (transfer din comanda) | da | `:15215` |
|
||||
| `26` (aviz din contract) | **nu** | — |
|
||||
| `8,9` (retur) | da | `:15240` |
|
||||
| `24` (aviz retur) | da | `:15245` |
|
||||
|
||||
**Confirmat: 23 si 41 au fiecare `Case` propriu in `Do Case`** (nu lipsesc din el, spre deosebire de
|
||||
tipul 52, verificat separat intr-o cercetare anterioara ca "nu intra in niciun `Case`") — dar niciunul
|
||||
din cele doua `Case`-uri nu seteaza `Visible = .T.`, deci butonul ramane la valoarea implicita
|
||||
ascunsa. Concluzie identica cu lista de preturi (1/5/7/10): "adauga tot" e ascuns azi, nu doar
|
||||
"neconfirmat".
|
||||
|
||||
### 2.2 Exista alt mecanism de adaugare in masa pe 23/41?
|
||||
|
||||
**Nu unul dedicat — dar exista mecanismul de baza, "adauga rand cu rand", disponibil pe orice tip.**
|
||||
`But_urmator1` (singular, nu "tot") e `ADD OBJECT`-at neconditionat in definitia clasei
|
||||
(`ofacturare.vc2:11237`), fara vreun `Visible=.F.` la design-time si fara sa fie atins de `Do Case`
|
||||
de la 15108-15248 (acolo se atinge doar `but_urmator2`, aferent `grd_contracte`/contract, scos prin
|
||||
`RemoveObject` pentru tipurile non-contract, `:15322-15323` — nu are legatura cu 23/41). Lantul e
|
||||
`But_urmator1.Click` → `do_urmator()` → `Thisform.do_adauga_articol()` (`:14715`) — exact metoda cu
|
||||
derivarea de TVA confirmata la intrebarea 1. Deci pe 23/41, ca si pe lista de preturi, operatorul
|
||||
adauga **o linie o data**, prin acelasi buton/metoda folosit si azi pe tipurile de lista de preturi —
|
||||
nu exista un "adauga tot" separat de recreat, pentru ca nu exista nici azi.
|
||||
|
||||
### 2.3 Cine mai foloseste `cursor_gestiune` — CORECTIE fata de premisa din plan
|
||||
|
||||
Rutarea reala e in `ofacturare.prg`, procedura `factureaza` (formularul **standard**,
|
||||
`frm_facturare_articole` — cel lansat implicit), `Do Case` de la `:266-308`:
|
||||
|
||||
```
|
||||
Case Inlist(tnTip, 1, 22, 5, 29, 7, 10, 23) && lista de preturi -> cursor_preturi (:279-282)
|
||||
Case Inlist(tnTip, 2, 26, 6, 52) && contract -> cursor_contract (:283-290)
|
||||
Case Inlist(tnTip, 3, 21, 25, 28, 42, 47) && comenzi -> cursor_comanda (:292-293)
|
||||
Case tnTip = 4 && din avize -> cursor_avize (:294-295)
|
||||
Case Inlist(tnTip, 41) && transfer/retur -> cursor_gestiune (:296-298)
|
||||
Case tnTip = 30 -> cursor_aviz_nir (:299-300)
|
||||
Case tnTip = 27 -> cursor_lucrare (:302-304)
|
||||
Case Inlist(tnTip, 8, 9, 24) -> cursor_retur (:306-307)
|
||||
```
|
||||
plus, mai sus in acelasi `Do Case` (`:271-278`): `Case Inlist(tnTip, 48, 49)` → **`cursor_articole_k`**
|
||||
(procedura complet diferita, nu una din cele cinci) si `Case tnTip = 45` → **`cursor_preturi`**.
|
||||
|
||||
**Pe formularul standard: doar tipul 41 foloseste `cursor_gestiune`.** Tipul **23 foloseste de fapt
|
||||
`cursor_preturi`**, grupat direct cu tipurile de lista de preturi (1,22,5,29,7,10) — asta explica de
|
||||
ce, la 2.1, tipul 23 se comporta identic cu lista de preturi (acelasi cursor sursa). Tipurile **45 si
|
||||
48/49 nu ating deloc `cursor_gestiune`**: 45 → `cursor_preturi`, 48/49 → `cursor_articole_k` (iese
|
||||
din perimetrul celor cinci cursoare din S4). Premisa din plan ("`cursor_gestiune` — tipurile 23/41")
|
||||
si mentiunea "45, 48, 49" (`s4_cautare_articole_server.md:344`, tabelul de vizibilitate) **descriu
|
||||
corect vizibilitatea butonului** (toate aceste tipuri au "adauga tot" ascuns), dar **nu si sursa lor
|
||||
de cursor** — nu toate trec prin `cursor_gestiune`.
|
||||
|
||||
**Pe formularul prototip** (`frm_facturare_articole2`/`factureaza2`, accesibil live doar cu optiunea
|
||||
`gnFacturareNou=1` **si** alegerea explicita a operatorului la un dialog de confirmare,
|
||||
`ofacturare.prg:88-93` — deci reachable in productie, nu cod mort, dar opt-in), rutarea **difera**:
|
||||
`Case Inlist(tnTip, 23, 41)` (`ofacturare.prg:776-778`) cheama **impreuna** `cursor_gestiune` pentru
|
||||
**ambele** tipuri. Pe acest formular, premisa din plan e exacta. Divergenta intre cele doua formulare
|
||||
(23 pe `cursor_preturi` in cel standard, pe `cursor_gestiune` in prototip) nu pare intentionata — nu
|
||||
exista niciun comentariu care s-o justifice — dar e in afara perimetrului acestei cercetari (read-only,
|
||||
fara editare) si nu afecteaza concluzia de mai jos, valabila pe oricare din cei doi cursori (niciunul
|
||||
nu are `id_jtva_coloana`, ambii sunt tratati identic de `do_adauga_articol`).
|
||||
|
||||
### 2.4 Concluzia care conteaza
|
||||
|
||||
**Dupa S4, pe tipurile 23/41 nu se pierde nimic functional.** "Adauga tot" e deja absent azi pe
|
||||
ambele (confirmat pe cod, 2.1), iar adaugarea se face deja rand-cu-rand prin acelasi mecanism folosit
|
||||
si pe lista de preturi (`but_urmator1`/`do_urmator`/`do_adauga_articol`, 2.2) — mecanism pe care S4 nu
|
||||
il atinge (S4 schimba doar sursa cursorului incarcat la deschidere, nu calea de adaugare a unei linii
|
||||
individuale). Aceeasi rezerva de la intrebarea 1 se aplica identic aici: valabil doar daca varianta
|
||||
filtrata a cautarii trece prin `do_adauga_articol`, nu daca reproduce `REPLACE`-ul direct din
|
||||
`combosql`.
|
||||
|
||||
---
|
||||
|
||||
## Handoff
|
||||
|
||||
Nu e necesara predare — ambele intrebari sunt inchise, cu dovada pe cod. Rezervele de la 1.4 si 2.4
|
||||
(derivarea `id_jtva_coloana` trebuie sa treaca prin `do_adauga_articol`/`frm_articol_factura.Init`,
|
||||
nu prin `REPLACE` direct tip `combosql`) sunt de dus mai departe in proiectarea S4, nu doar de
|
||||
arhivat aici. Corectia de la 2.3 (doar tipul 41, nu si 23, foloseste `cursor_gestiune` pe formularul
|
||||
standard) merita reflectata daca planul S4 mai citeaza undeva "cursor_gestiune (23/41)" ca sursa unica.
|
||||
606
docs/cercetare/s4b_bara_butoane_meniu.md
Normal file
606
docs/cercetare/s4b_bara_butoane_meniu.md
Normal file
@@ -0,0 +1,606 @@
|
||||
# S4b — Bara de butoane si meniul de adaugare
|
||||
|
||||
Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit), pentru
|
||||
povestea **S4b** din `docs\plan_13_unificare_formular_facturare.md:1890-1899` (textul decis in
|
||||
sectiunea J, `:911-1101`). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si
|
||||
`COMUN\programe\ofacturare_editare.prg` (perimetrul #6) — doar citite, cand au aparut in cautari.
|
||||
|
||||
## Verdict (esenta, 10 randuri)
|
||||
|
||||
Cele trei bucati din decizia 13 sunt implementabile fara cod nou major, dar cu **trei corectii fata
|
||||
de textul din plan**, toate in favoarea utilizatorului: (1) golul de contract nu e doar
|
||||
`but_urmator_tot1.Visible` lipsa pe tip 2/6/26 — pe **tip 52 formularul nu intra deloc in ramura
|
||||
lui**, `Do Case` nu are niciun `Case` care sa-l prinda, deci pierde si titlul, si eliminarea coloanei
|
||||
`cSerie`, nu doar butonul "tot"; (2) tiparul RORIS (`frm_tranzit`, dialog bespoke) **nu e cel mai
|
||||
ieftin de refolosit** — suita are deja, folosit chiar in acest formular pentru retur
|
||||
(`do_cauta_facturi`), un mecanism generic de selectie multipla cu bifare, `cauta_alfa(...,
|
||||
tnTipReturn=1)`, mai aproape de cerinta („dialog modal cu coloana de bifat si criterii de cautare")
|
||||
decat un formular nou; (3) „unitatea de selectie e rata" pe contract **nu e valabil pentru toate
|
||||
contractele** — cursorul `crsarticole1` are doua ramuri disjuncte, `OPT_FACTURARE=3` (articole reale)
|
||||
si `OPT_FACTURARE IN (1,2)` (rate de scadentar), iar un contract e mereu pe una singura; eticheta
|
||||
„Alege ratele de facturat…" e corecta doar pe ramura a doua. Echivalenta „tot" = „alege total" tine
|
||||
azi pe o singura rutina comuna, `do_adauga_articol`, dar `do_adauga_tot` **nu parcurge `crsarticole1`
|
||||
deloc** — pe contract, „adauga tot" ar trebui sa fie scris, nu doar facut vizibil. Detaliile, cu
|
||||
`fisier:linie`, mai jos.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventarul butoanelor de azi
|
||||
|
||||
### 1.1 `frm_facturare_articole` (formularul de compunere in productie, `ofacturare.vc2:10968-15739`)
|
||||
|
||||
Grid sursa (comanda/lista preturi) = `grd_articole` (`RecordSource=crsarticole`,
|
||||
`ofacturare.vc2:11570`). Grid sursa-contract = `grd_contracte` (`RecordSource=crsarticole1`,
|
||||
`:11891`). Grid destinatie (linii factura) = `grd_factura` (`RecordSource=crsfactura`, `:12263`).
|
||||
|
||||
| Control | Clasa | Caption/Picture | L/T/W/H | ToolTipText | `fisier:linie` |
|
||||
|---|---|---|---|---|---|
|
||||
| `But_modifica1` | `but_modifica` | (fara caption) `modific_sus.bmp` | 773/61/30/27 | "Modificare (CTRL+M)" | `:11192` |
|
||||
| `But_sterge1` | `but_sterge` | (fara caption) `sterg_sus.bmp` | 811/61/30/27 | "Stergere (CTRL+D)" | `:11229` |
|
||||
| `But_renunt1` | `but_renunt` | (fara caption) `renunt_sus.bmp` | 792/1/30/27 | "Renuntare (ESC)" | `:11202` |
|
||||
| `But_reset1` | `but_reset` | (fara caption) `reset_sus.bmp` | 303/295/30/27 | "Reseteaza" | `:11213` |
|
||||
| `But_urmator1` | `but_urmator`, `caction=do_urmator` | `urmator1.bmp` | 343/348/30/27 | "Verificare" | `:11237` |
|
||||
| `But_urmator2` | `but_urmator`, `caction=do_urmator2` | `urmator1.bmp` | 343/143/30/27 | "Verificare" | `:11247` |
|
||||
| **`But_urmator_tot1`** | `but_urmator_tot`, `caction=do_adauga_tot` | `urmator1_tot.bmp`, **fara Caption, fara ToolTipText** (clasa insasi nu are niciuna din ele — `cmd_butoane.vc2:414-425`) | 343/378/30/27, `Visible=.F.` implicit | — | `:11257` |
|
||||
| `But_retur` | `but_retur`, `caction=do_retur` | `retur1.bmp`, `Visible=.F.` implicit | 343/407/30/27 | "Retur" | `:11221` |
|
||||
|
||||
Niciun buton "linie noua" (`but_nou`) — o linie se adauga alegand un rand din `grd_articole` /
|
||||
`grd_contracte` si apeland `do_adauga_articol` (dublu-click / Enter, mecanism de grid standard, nu
|
||||
citit exhaustiv aici), nu prin `APPEND BLANK` pe `crsfactura`. "Detalii linie" nu exista ca buton
|
||||
separat — `But_modifica1` **e** azi echivalentul: deschide `frm_articol_factura` pe randul curent din
|
||||
`crsfactura` (`do_modifica`, `:13746-13914`), reface plafonul de cantitate din cursorul sursa
|
||||
(`crsarticole` sau `crsarticole1`, ales prin `poArticol.opt_facturare`, `:13778`) si reconstruieste
|
||||
proprietatile de discount/pret in valuta.
|
||||
|
||||
**"Adauga tot" — `But_urmator_tot1.Click -> do_adauga_tot`** (`:13169-13198`):
|
||||
|
||||
```
|
||||
PROCEDURE do_adauga_tot
|
||||
If Used('crsarticole') And Reccount('crsarticole') > 0
|
||||
Select crsarticole
|
||||
Scan
|
||||
...
|
||||
Thisform.do_adauga_articol(.T.) && tlContract = omis => .F. implicit
|
||||
...
|
||||
Endscan
|
||||
Else
|
||||
aMessageBox("Nu exista articole de adaugat!", 48, "Atentie")
|
||||
Endif
|
||||
ENDPROC
|
||||
```
|
||||
|
||||
**Parcurge exclusiv `crsarticole`.** Nu exista nicio ramura care sa parcurga `crsarticole1` — deci
|
||||
chiar daca s-ar face vizibil pe tip 2/6/26/52, "adauga tot" **nu ar aduce nimic** pe un contract al
|
||||
carui `grd_contracte`/`crsarticole1` are randuri (rate sau articole de contract), pentru ca butonul
|
||||
citeste doar cursorul celalalt. E un gol de **cod**, nu doar de vizibilitate — vezi sectiunea 5.
|
||||
|
||||
**Conventia de clase de butoane** (confirmat, `inventar_controale_formulare.md`): clasa de baza
|
||||
`buton` (`_cmd_base.vc2:16`) are `Caption=""` implicit — butoanele sunt doar-imagine 30x27px prin
|
||||
design. `but_nou` (`cmd_butoane.vc2:214-227`, `caption=do_adauga`, `ToolTipText="Adaugare
|
||||
(CTRL+N)"`) si `but_sterge` (`:354-366`, `caption=inainte_de_do_sterge`, tot fara Caption propriu la
|
||||
nivel de clasa) au deja `ToolTipText`, dar niciodata `Caption` — "eticheta, nu iconita muta" ceruta de
|
||||
decizia 13 e o schimbare reala, nu o simpla refolosire a clasei: fie se seteaza `Caption` pe instanta
|
||||
(clasa `buton` are proprietatea, pur si simplu nefolosita azi), fie se creeaza o varianta de clasa cu
|
||||
`Caption` implicit.
|
||||
|
||||
### 1.2 `frm_facturare_articole2` (prototip, `ofacturare.vc2:15741-19355`)
|
||||
|
||||
Arhitectura diferita: **un singur grid** `grd_factura` cu editare inline prin combo-uri in celule
|
||||
(`cCodMat.cboCodmat`, `cDenumire.cCboDenumire`) — nu exista `grd_articole`/`crsarticole` separat.
|
||||
|
||||
| Control | Clasa | Caption/Picture | L/T/W/H | ToolTipText | `fisier:linie` |
|
||||
|---|---|---|---|---|---|
|
||||
| **`But_nou1`** | `but_nou` (caption suprascris pe instanta, vezi mai jos) | `nou_sus.bmp` | 719/175/30/27 | "Adaugare (CTRL+N)" | `:15936` |
|
||||
| `But_sterge1` | `but_sterge` | `sterg_sus.bmp` | 747/175/30/27 | "Stergere (CTRL+D)" | `:15955` |
|
||||
| `But_renunt1` | `but_renunt` | `renunt_sus.bmp` | 716/1/30/27 | "Renuntare (ESC)" | `:15944` |
|
||||
|
||||
**Sunt deja unde trebuie**: `Top=175`, gridul la `Top=204` (`:15936`, `:15955`) — **deasupra
|
||||
tabelului**, exact pozitionarea ceruta de decizia 13.
|
||||
|
||||
**`But_nou1.Click -> do_adauga`** (`:17118-17122`, override propriu pe instanta, nu `caction`
|
||||
mostenit de la clasa `but_nou`):
|
||||
|
||||
```
|
||||
PROCEDURE do_adauga
|
||||
Select crsFactura
|
||||
APPEND BLANK
|
||||
this.grd_factura.SetFocus()
|
||||
ENDPROC
|
||||
```
|
||||
|
||||
Adauga direct un rand gol si da focus in grid — **nu** cheama `do_adauga_articol`, nu verifica stoc,
|
||||
nu deschide niciun dialog. Completarea articolului se face **dupa**, prin editare inline in celule
|
||||
(`cboCodmat`/`cCboDenumire`) — acesta e tiparul care se leaga direct de S4 (cautare pe server, vezi
|
||||
sectiunea 6), nu de meniul de adaugare in masa.
|
||||
|
||||
Nu exista `But_modifica`/`detalii linie` in acest prototip — editarea se face inline, in celula.
|
||||
|
||||
### 1.3 Tabel comparativ
|
||||
|
||||
| | `frm_facturare_articole` (productie) | `frm_facturare_articole2` (prototip) |
|
||||
|---|---|---|
|
||||
| Pozitia butoanelor de linie | lateral, intre cele doua griduri (`Left=773+`) | deasupra gridului (`Top=175`, gridul la `Top=204`) — cerinta decizie 13 |
|
||||
| "Linie noua" | nu exista — se alege dintr-un grid sursa | `But_nou1` -> `APPEND BLANK` + focus |
|
||||
| "Sterge linie" | `But_sterge1`, lateral | `But_sterge1`, deasupra |
|
||||
| "Detalii linie" | `But_modifica1` -> `frm_articol_factura` (dialog complet) | nu exista — editare inline |
|
||||
| Sursa de articole | 2 griduri separate, `crsarticole`/`crsarticole1` | niciunul — combo pe cod/denumire in celula |
|
||||
| Caption pe butoane | fara, peste tot (doar iconite) | fara, peste tot (doar iconite) — nici prototipul nu rezolva "eticheta" |
|
||||
|
||||
Concluzie pentru implementare: **pozitionarea** se ia din prototip, **comportamentul de adaugare in
|
||||
masa** (`do_adauga_tot`/dialoage de alegere) se ia din formularul de productie (prototipul nu are deloc
|
||||
aceasta logica — n-a fost construit pentru documente cu sursa), iar **eticheta pe buton** nu exista
|
||||
azi in niciunul din cele doua, e de adaugat explicit in ambele cazuri.
|
||||
|
||||
---
|
||||
|
||||
## 2. `xmenu()` — contract si exemple
|
||||
|
||||
**Definitie**: `COMUN\programe\proceduri_comune.prg:755-822` (identica, byte-cu-byte in structura, cu
|
||||
`COMUN\programe\oproceduri_comune.prg:1550-1617` — a doua e cea incarcata efectiv, SET PROCEDURE
|
||||
ADDITIVE peste prima, dar comportamentul e acelasi).
|
||||
|
||||
```
|
||||
Procedure XMENU
|
||||
Lparameters TCITEMS, TNBAR
|
||||
&& TCITEMS = optiuni separate prin ";" ; "\-" = separator vizual (bara, nu optiune selectabila,
|
||||
&& nu se trece prin ALLTRIM); "\<X" in fata unei litere = accelerator de tastatura
|
||||
&& TNBAR = optiunea initial selectata (default 1)
|
||||
...
|
||||
RETURN IIF( LASTKEY()=27, 0, m.NSELECT ) && 0 la ESC sau la orice tastatura care nu alege
|
||||
ENDPROC
|
||||
```
|
||||
|
||||
- Popup-ul apare la pozitia mouse-ului (`Mrow()/Mcol()`), cu `DEFINE BAR` cate unul per optiune.
|
||||
- Selectia se prinde in `GETCHOICE` (`oproceduri_comune.prg:1660-1666`): `m.NSELECT = Bar()`.
|
||||
- **Returul e 1-based** (numarul barei alese), **`0` daca s-a apasat ESC** (`Lastkey()=27`) — verificat
|
||||
prin `Empty(m.lnOptiune)` in tot codul (0 e Empty pentru numeric).
|
||||
- Nu exista parametru de "titlu" separat — primul item e primul rand din popup, nu un header.
|
||||
|
||||
**Trei exemple reale, gata de copiat:**
|
||||
|
||||
1. **`inainte_de_do_modifica`** (`ofacturare_comun.vc2:4928-4935`) — meniu static, doua optiuni,
|
||||
exact tiparul pe care planul il citeaza ca precedent pentru butonul unic:
|
||||
```
|
||||
PROCEDURE inainte_de_do_modifica
|
||||
Local lnOptiune
|
||||
lnOptiune = xmenu('Modificare \<date factura;Editare \<factura (articole, cantitati, preturi)')
|
||||
Do Case
|
||||
Case lnOptiune = 1
|
||||
This.do_modifica()
|
||||
Case lnOptiune = 2
|
||||
This.do_editare_factura()
|
||||
Endcase
|
||||
ENDPROC
|
||||
```
|
||||
|
||||
2. **`do_verifica`** (`ofacturare_comun.vc2:4879-4886`) — meniu static, trei optiuni, cu accelerator
|
||||
pe fiecare literal:
|
||||
```
|
||||
lnOptiune = xmenu('Verifica codurile fiscale la fiecare \<data a facturilor;Verifica doar la \<inceputul lunii;Verifica doar la \<sfarsitul lunii')
|
||||
IF EMPTY(m.lnOptiune)
|
||||
RETURN
|
||||
ENDIF
|
||||
```
|
||||
|
||||
3. **Meniul de borderouri** (`ofacturare_comun.vc2:5006-5029`) — meniu **construit dinamic**, cu
|
||||
optiuni suplimentare adaugate conditionat si un separator vizual `\-`:
|
||||
```
|
||||
lcMeniuPlus = ''
|
||||
IF !(EMPTY(m.lnOptiuniPlus) OR ... )
|
||||
FOR lnOptiunePlus = 1 TO m.lnOptiuniPlus
|
||||
lcTitlu = ALLTRIM(GETWORDNUM(m.lcListaMeniu, m.lnOptiunePlus, '|'))
|
||||
lcMeniuPlus = m.lcMeniuPlus + ';' + m.lcTitlu
|
||||
ENDFOR
|
||||
ENDIF
|
||||
lcMeniu = m.lcMeniu + IIF(!EMPTY(m.lcMeniuPlus), ';\-' + m.lcMeniuPlus, '')
|
||||
lnOptiune = xmenu(m.lcMeniu)
|
||||
If Empty(m.lnOptiune)
|
||||
Return
|
||||
Endif
|
||||
DO CASE
|
||||
CASE m.lnOptiune = 1
|
||||
...
|
||||
```
|
||||
Acesta e tiparul direct aplicabil la S4b: `lcMeniu` se construieste cu `Do Case poDate.tip` (vezi
|
||||
sectiunea 3), apoi un singur `xmenu(lcMeniu)` si un `Do Case lnOptiune = N` care ramifica pe
|
||||
pozitia in lista construita — **nu** pe numar fix, ca la exemplele 1-2, pentru ca lista variaza pe
|
||||
tip. Trebuie tinut sincron `lcMeniu` (textul optiunii) cu ordinea in care se evalueaza `lnOptiune`
|
||||
in `Do Case`, altfel un `Case lnOptiune = 3` nimereste alta optiune decat cea afisata — riscul
|
||||
concret al unui meniu dinamic, absent la cele statice.
|
||||
|
||||
**Ce NU ofera `xmenu()`**: niciun mecanism de dezactivare a unei optiuni individuale (doar prezenta/
|
||||
absenta din lista construita), nicio pictograma pe item, niciun submeniu. Pentru meniul din decizia 13
|
||||
(4-5 optiuni pe sursa, variabile) e suficient — nu e nevoie de mai mult.
|
||||
|
||||
---
|
||||
|
||||
## 3. Conditiile de vizibilitate ale meniului, pe tip de document
|
||||
|
||||
Sursa: `frm_facturare_articole.Init`, `Do Case poDate.eProforma / poDate.lCopiere / poDate.tip`,
|
||||
`ofacturare.vc2:15109-15248` (citit integral, nu esantion).
|
||||
|
||||
| Ramura (`Case`) | `tip`(uri) | Titlu | `but_urmator_tot1.Visible` | `but_retur.Visible` | `fisier:linie` |
|
||||
|---|---|---|---|---|---|
|
||||
| `eProforma = 1` | proforma (orice tip) | "PROFORMA" | **.T.** | — | `:15110-15113` |
|
||||
| `lCopiere` | copiere factura/aviz | "COPIERE FACTURA/AVIZ ..." | **.T.** | — | `:15115-15120` |
|
||||
| `Inlist(tip,1,5,7,10)` | lista de preturi (lei/valuta/credit note/fiscala valuta) | — | — | **.T.** | `:15122-15127` |
|
||||
| `Inlist(tip,2,6)` | **contract** (lei/valuta) | "FACTURA PE CTR. ..." | — (nesetat, ramane `.F.`) | — | `:15129-15143` |
|
||||
| `tip = 3` | comanda | "FACTURA LA COMANDA ..." | **.T.** | — | `:15144-15150` |
|
||||
| `tip = 4` | din avize | "FACTURA DIN AVIZE" | **.T.** | — | `:15151-15167` |
|
||||
| `Inlist(tip,21,28,42,47)` | aviz catre clienti din comanda | "AVIZ DE EXPEDITIE DIN COMANDA" | **.T.** | — | `:15168-15177` |
|
||||
| `Inlist(tip,22,29)` | aviz din lista de preturi | "AVIZ DE EXPEDITIE DIN LISTA DE PRETURI" | — | — | `:15178-15186` |
|
||||
| `tip = 23` | transfer subunitati | "TRANSFER INTRE SUBUNITATI" | — | — | `:15187-15195` |
|
||||
| `tip = 41` | retur transfer | "RETUR TRANSFER" | — | — | `:15196-15205` |
|
||||
| `tip = 25` | transfer din comanda | "TRANSFER INTRE SUBUNITATI PE BAZA DE COMANDA" | **.T.** | — | `:15206-15215` |
|
||||
| `tip = 26` | **aviz din contract** | "AVIZ DE EXPEDITIE DIN CONTRACTUL ..." | — (nesetat) | — | `:15216-15234` |
|
||||
| `Inlist(tip,8,9)` | factura retur lei/valuta | — | **.T.** | — | `:15236-15241` |
|
||||
| `tip = 24` | aviz retur | "RETUR AVIZ DE EXPEDITIE" | **.T.** | — | `:15242-15248` |
|
||||
| *(niciun Case)* | **`tip = 52`** | **nesetat — ramane titlul implicit al formularului** | **nesetat** | — | — |
|
||||
|
||||
**Corectie fata de plan** (`plan_13...md:924-926`, `:1882-1883`): planul spune "tipurile de contract
|
||||
(2, 6, 26, 52) nu sunt in lista [de vizibilitate a `but_urmator_tot1`]". E adevarat pentru 2/6/26, dar
|
||||
**incomplet pentru 52**: `poDate.tip = 52` nu apare in **niciun** `Case` al acestui `Do Case` — nu
|
||||
doar ca nu primeste `but_urmator_tot1.Visible = .T.`, ci **nu primeste nimic**: nu i se seteaza
|
||||
titlul (`lb_titlu_alb_b121.Caption` ramane cel implicit al formularului, probabil gol sau invechit),
|
||||
nu i se scoate coloana `cSerie` din grid (ramane vizibila, desi contractul in valuta n-are serie de
|
||||
lot relevanta aici), nu i se schimba eticheta coloanei de cantitate. Pe cod, tip 52 se comporta azi ca
|
||||
un tip necunoscut care a ajuns totusi sa deschida formularul (posibil pentru ca fluxul Oracle il
|
||||
recunoaste — `Inlist(tnTip,2,26,6,52)` la `cursor_contract`, `ofacturare.prg:283` — dar formularul de
|
||||
articole nu l-a "prins" niciodata in `Do Case`-ul lui local). **Consecinta pentru S4b**: reparatia
|
||||
corecta nu e "adauga 52 la linia lui 2/6" (ar ramane fara titlu/fara eliminare `cSerie`), ci
|
||||
**adauga-l explicit ca al treilea membru al ramurii `Inlist(poDate.tip, 2, 6)`** de la `:15129`,
|
||||
identic cu cum a fost tratat deja in `frm_date_factura.Init` (`ofacturare.vc2:9530`:
|
||||
`Case Inlist(poDate.tip, 2, 6, 52)`, si inca o data la `:9632`, `:9670`) — **acolo tip 52 e deja
|
||||
grupat corect cu 2/6**, doar in acest formular de articole a fost omis.
|
||||
|
||||
**Ce inseamna gruparea 2/6/26/52 pentru continutul meniului**: toate patru sunt "contract" — 2/6 =
|
||||
factura pe contract (lei/valuta), 26 = aviz pe contract, 52 = factura fiscala in valuta pe contract
|
||||
(`COMUN\docs\tipuri_documente_facturare.md:23,27,38,50`). Optiunile din tabelul J raman aceleasi
|
||||
pentru toate patru (schimba doar cuvantul "Adauga"/"Alege" vs. eventual "Avizeaza" pe 26, daca se
|
||||
pastreaza conventia de limbaj factura/aviz existenta in restul formularului — `lcTipDoc` calculeaza
|
||||
deja acest cuvant in alte ramuri ale aceluiasi `Do Case`, e.g. `:15175,15185,15194`, dar **nu e setat
|
||||
deloc pe ramura contract** — inca o mica omisiune, `lcTipDoc` ramane la valoarea implicita "factura"
|
||||
si pe aviz de contract).
|
||||
|
||||
**Meniul propus, per sursa** (reluat din tabelul J, cu corectiile de mai sus):
|
||||
|
||||
| Sursa (`tip`) | Optiunile din `xmenu()` |
|
||||
|---|---|
|
||||
| lista de preturi (1,5,7,10) | Cauta in lista de preturi… · Alege din nomenclator… · Retur de articole… |
|
||||
| **contract, `OPT_FACTURARE=3`** (2,6,26,52) | Adauga tot din contract · Alege articolele din contract… · Cauta in lista de preturi… · Alege din nomenclator… |
|
||||
| **contract, `OPT_FACTURARE IN (1,2)`** (2,6,26,52) | Adauga toate ratele · **Alege ratele de facturat…** · Cauta in lista de preturi… · Alege din nomenclator… |
|
||||
| comanda (3) | Adauga tot din comanda · Alege din comanda… · Cauta in lista de preturi… · Alege din nomenclator… |
|
||||
| avize (4) / avize din comanda (21,28,42,47) / transfer din comanda (25) | Adauga tot din avize · Alege avizele… · Alege liniile din avize… · Cauta in lista de preturi… · Alege din nomenclator… |
|
||||
| retur (8,9,24) | Adauga tot din facturile alese · Alege liniile de returnat… · Cauta in lista de preturi… · Alege din nomenclator… |
|
||||
|
||||
Diferenta fata de tabelul din plan e explicata in sectiunea 4 (contractul are doua meniuri posibile,
|
||||
nu unul) si in sectiunea 9 (retur nu are azi un pas separat "alege facturile" la nivelul acestui
|
||||
formular — vezi mai jos).
|
||||
|
||||
---
|
||||
|
||||
## 4. Dialogul de alegere selectiva
|
||||
|
||||
### 4.1 Nu porni de la `frm_tranzit` (RORIS) — porneste de la `cauta_alfa`
|
||||
|
||||
Raportul de referinta (`COMUN\docs\cercetare\import_roris_roaacnpro.md`) descrie corect **arhitectura
|
||||
generala** (buton conditionat -> metoda unica -> dialog cu bifare si criterii -> populare aditiva prin
|
||||
`INSERT`, fara scriere Oracle pana la salvare) — dar `frm_tranzit` insusi e un formular **specific
|
||||
ROAACNPRO**, care nu exista in ROAFACTURARE si n-ar trebui portat ca formular.
|
||||
|
||||
**ROAFACTURARE are deja, in productie, in acelasi `frm_facturare_articole` care e subiectul acestei
|
||||
povesti, mecanismul cerut** — `cauta_alfa()` (`COMUN\programe\cauta_alfa.prg:16-260`), functia de
|
||||
cautare generica folosita de zeci de `do_cauta_*` din toata suita. Are deja tot ce cere decizia 13:
|
||||
|
||||
- **coloana de bifat**: cand `tnTipReturn=1`, `cauta_alfa` adauga singura o coloana `ales` peste
|
||||
cursorul de rezultate (`cauta_alfa.prg:124`: `Select *, 0 As ales From &lcCursort ... Into Cursor
|
||||
&lcCursor`) si titlul devine explicit "Alegeti ... (mouse-click pe numar sau apasati SPACE)"
|
||||
(`oproceduri_facturare.prg:2104`);
|
||||
- **criterii de cautare**: parametrul `tcStringCriterii` deschide `cauta_alfa_form_plus`, varianta cu
|
||||
bara de criterii (`cauta_alfa.prg:22`); fara el, cautarea alfa/browse standard tot filtreaza
|
||||
interactiv pe coloanele afisate;
|
||||
- **populare aditiva, fara scriere in baza**: returul (`tnTipReturn=1`) e un XML cu randurile bifate,
|
||||
convertit local prin `Xmltocursor(...)` (exemplu direct, `ofacturare.vc2:9196`) — nimic nu ajunge la
|
||||
Oracle in acest pas;
|
||||
- **e deja folosit in acest exact formular**, pentru un caz de "alege documentul sursa": vezi 4.2.
|
||||
|
||||
**Recomandare de proiectare**: dialoagele "Alege..." din meniul S4b se construiesc pe reteta
|
||||
`cauta_alfa(..., tnTipReturn=1)`, cu `tcselect`/`tcfiltru` diferite pe sursa (comanda/contract/avize/
|
||||
retur), **nu** pe un formular nou stilizat dupa `frm_tranzit`. Ramane de portat din RORIS doar
|
||||
**ideea**, nu codul: buton unic condiționat, metoda de intrare unica, populare aditiva — toate trei
|
||||
deja adevarate si pentru `cauta_alfa`.
|
||||
|
||||
### 4.2 Precedentul direct: retur multi-factura
|
||||
|
||||
`frm_date_factura.do_cauta_facturi` (`ofacturare.vc2:9173-9212`) foloseste deja exact acest tipar,
|
||||
pentru cazul "alege mai multe facturi sursa de retur":
|
||||
|
||||
```
|
||||
lcXMLFacturi = caut_facturi_multiple_client(poDate.id_client, poDate.in_valuta, poDate.id_valuta, .T.)
|
||||
If !Empty(lcXMLFacturi) and gnButon = 1
|
||||
Xmltocursor(lcXMLFacturi, "crsFacturiTemp")
|
||||
...
|
||||
poDate.listaid = cursor2lista("crsFacturiTemp", "id_vanzare", ",")
|
||||
```
|
||||
|
||||
si `caut_facturi_multiple_client` (`oproceduri_facturare.prg:2091-2121`) cheama direct
|
||||
`cauta_alfa(..., lnTipReturn = Iif(tlFacturiMultiple, 1, 0))`. **Important pentru sectiunea 9**: acest
|
||||
pas ruleaza azi la **antet** (`frm_date_factura`), inainte ca formularul de articole (si deci bara de
|
||||
butoane S4b) sa existe — `poDate.listaid` e fixat o singura data, cursorul `crsarticole` se incarca
|
||||
o singura data din el (`cursor_retur_document`, cu `V_LISTAID`). Optiunea "Adauga tot din facturile
|
||||
alese" din meniul S4b **poate** refolosi direct `do_adauga_tot`/`crsarticole` asa cum e azi (sursa e
|
||||
deja bounded). O optiune noua "Alege facturile de returnat…" **la nivelul barei de butoane** ar insemna
|
||||
insa a permite schimbarea setului de facturi sursa **dupa** ce formularul de articole s-a deschis —
|
||||
azi nu exista acest flux (odata setat, `poDate.listaid` nu se rescrie din articole). E o decizie de
|
||||
proiectare noua, nu o simpla mutare a codului existent (vezi 9.3).
|
||||
|
||||
### 4.3 Ce cursor-sursa pe fiecare tip, si ce inseamna "linie" acolo
|
||||
|
||||
| Sursa | Cursor(oare) populat(e) la intrarea in `factureaza`/`factureaza2` | Ce e o "linie" pentru dialogul de alegere | `fisier:linie` |
|
||||
|---|---|---|---|
|
||||
| **comanda** (3, 21, 25, 28, 42, 47) | `crsarticole` <- `pack_facturare.cursor_comanda` | un articol de pe `COMENZI_ELEMENTE`, cu `id_pol` mostenit direct de pe linia comenzii | `ofacturare.prg:292-293` |
|
||||
| **contract, `OPT_FACTURARE=3`** (2,6,26,52) | `crsarticole1` <- `pack_facturare.cursor_contract`, ramura `CTR_ARTICOLE` | un articol al contractului, `id_pol` derivat prin `CTR_ARTICOLE.ID_POL_ART -> CRM_POLITICI_PRET_ART` | `ff_...COMUN_PACK_FACTURARE.sql:2752-2836` |
|
||||
| **contract, `OPT_FACTURARE IN (1,2)`** (2,6,26,52) | `crsarticole1` <- acelasi cursor, ramura `CTR_SCADENTAR` (`UNION ALL`) | o **rata** de scadentar (`ID_RATA`, `NR_RATA`, `DATA_RATA`, `DATA_SCADENTA`, `DEN_RATA`); `id_articol` e **NULL**, `cantitate` e mereu 1, `gestionabil=0` | `ff_...COMUN_PACK_FACTURARE.sql:2836-2924` |
|
||||
| **contract, oricare din cele doua** — cautare libera | `crsarticole` <- acelasi apel, dar cu `pack_facturare.cursor_preturi` legat in interior | lista de preturi normala a operatorului (nu e "libera de politica", vezi `idpol_comanda_contract.md` E.3) | `ff_...COMUN_PACK_FACTURARE.sql:2940-2948` |
|
||||
| **avize** (4) | `crsarticole` <- `pack_facturare.cursor_avize` | un rand de pe avizul sursa | `ofacturare.prg:294-295` |
|
||||
| **retur** (8, 9, 24) | `crsarticole` <- `pack_facturare.cursor_retur_document`, filtrat pe `V_LISTAID` (facturile alese la antet, 4.2) | un articol al facturii/facturilor deja alese; **nicio coloana `id_vanzare`/`id_vanzare_det` in cursor** — legatura cu factura sursa se pierde inainte sa ajunga in VFP (`docs\cercetare\legatura_linie_retur.md:8-20`) | `ff_...COMUN_PACK_FACTURARE.sql:3949-4062` |
|
||||
|
||||
**Pe contract, `crsarticole1` e populat printr-un singur `UNION ALL`, dar cele doua ramuri sunt
|
||||
disjuncte pe `OPT_FACTURARE`**: pentru un contract cu `OPT_FACTURARE=3`, `WHERE A.OPT_FACTURARE = 3`
|
||||
elimina ramura de rate; pentru `OPT_FACTURARE IN (1,2)`, `WHERE ... IN (1,2)` elimina ramura de
|
||||
articole. **Un contract nu produce niciodata ambele tipuri de rand in acelasi `crsarticole1`.** Deci
|
||||
eticheta din meniu ("Adauga tot din contract" vs. "Adauga toate ratele") **trebuie sa depinda de
|
||||
`crsarticole1.opt_facturare`** (coloana exista in cursor, `:2751,2893` — poate fi citita din primul
|
||||
rand dupa incarcare), nu poate fi un text static ca in tabelul din plan.
|
||||
|
||||
**Daca `crsarticole1` iese gol** (contract fara articole/rate configurate), formularul de azi
|
||||
**elimina complet** `grd_contracte`/`But_urmator2` si redistribuie inaltimea catre `grd_articole`
|
||||
(`ofacturare.vc2:15294-15324`, `If !Used('crsarticole1') ... Thisform.RemoveObject('grd_contracte')`)
|
||||
— documentul se comporta ca o factura "libera" din lista de preturi. Meniul S4b trebuie sa verifice
|
||||
aceeasi conditie inainte sa ofere "Adauga tot din contract"/"Alege ratele…": daca cursorul nu exista
|
||||
sau e gol, optiunea nu apare deloc (nu apare goala).
|
||||
|
||||
### 4.4 Populare aditiva si "nicio scriere pana la salvare"
|
||||
|
||||
Deja adevarat prin constructie, pentru toata familia `do_adauga_articol`: fiecare rand ales (fie prin
|
||||
`do_adauga_tot`, fie prin rezultatul unui `cauta_alfa`) trece prin **`Thisform.do_adauga_articol(...)`**,
|
||||
care termina cu `Insert Into crsfactura(...)` (cursor local, `.T. Buffering`) — niciun apel Oracle in
|
||||
acest pas (Oracle e atins abia la salvarea documentului, `finalizeaza_factura`/`scrie_factura2`, in
|
||||
afara acestei povesti). Un dialog nou de alegere trebuie doar sa **produca lista de randuri bifate**
|
||||
(cursor XML din `cauta_alfa`) si sa **cheme aceeasi rutina** rand cu rand — vezi sectiunea 5, e
|
||||
punctul care garanteaza si echivalenta cu "tot".
|
||||
|
||||
### 4.5 Zero rezultate — mesaj, nu tacere
|
||||
|
||||
RORIS nu spune nimic la zero rezultate (`import_roris_roaacnpro.md`, punctul 5: butonul "pare ca nu
|
||||
face nimic"). `cauta_alfa` insasi nu are un mesaj dedicat de "zero rezultate" (e un browse generic —
|
||||
daca cursorul de rezultate e gol, grila apare goala, fara alt semnal, cf. structurii ei standard).
|
||||
**Reteta pentru S4b**: verificarea "zero rezultate" se face **inainte** de a deschide `cauta_alfa`
|
||||
(similar cu `do_cauta_facturi`, care verifica intai `Empty(poDate.id_client)` inainte de a cauta),
|
||||
printr-un `SELECT COUNT(*)` (sau verificare pe cursorul deja incarcat, `Reccount(...)=0`) urmat de
|
||||
`AMESSAGEBOX` care spune **de ce**: "Contractul nu are rate de facturat neincasate" / "Comanda nu are
|
||||
articole ramase de facturat" / etc., in loc sa lase dialogul sa se deschida gol. Exact tiparul deja
|
||||
folosit de `do_adauga_tot` insusi pe ramura fara date (`:13195-13197`,
|
||||
`"Nu exista articole de adaugat!"`) — se extinde acelasi obicei la dialogul de alegere.
|
||||
|
||||
---
|
||||
|
||||
## 5. Echivalenta „tot" vs. „alege"
|
||||
|
||||
**Garantia structurala exista, dar e incompleta azi.** Toate cele trei cai de populare a
|
||||
`crsfactura` — `do_adauga_tot` (SCAN + apel), un viitor dialog de alegere (bifare + apel pe fiecare
|
||||
rand bifat), si adaugarea manuala rand cu rand — converg spre **aceeasi rutina**,
|
||||
`Thisform.do_adauga_articol(tlImplicit, tlContract, tlRetur)` (`ofacturare.vc2:12813-...`). Cata vreme
|
||||
toate trei cheama exact aceasta rutina, cu acelasi cursor sursa pozitionat pe randul corect, rezultatul
|
||||
per linie in `crsfactura` e identic prin constructie — nu exista o a doua cale de `INSERT` in
|
||||
`crsfactura` in afara acestei rutine (confirmat prin cautarea `Insert Into crsfactura` — singurul alt
|
||||
loc e ramura specifica `tip=30`/aviz din NIR, `ofacturare.prg:357-376`, care nu intra in perimetrul
|
||||
S4b).
|
||||
|
||||
**Ce lipseste, concret, ca sa fie adevarat si pe contract**: `do_adauga_tot` (`:13169-13198`) parcurge
|
||||
**doar `crsarticole`**. Pentru ca "Adauga tot din contract" / "Adauga toate ratele" sa functioneze,
|
||||
`do_adauga_tot` are nevoie de o ramura noua (sau un al doilea parametru `tlContract`, simetric cu
|
||||
`do_adauga_articol`) care sa faca `SCAN` peste `crsarticole1` si sa cheme
|
||||
`Thisform.do_adauga_articol(.T., .T.)`. Fara aceasta completare, "tot" pe contract fie nu exista, fie
|
||||
(daca cineva ar face butonul vizibil fara sa atinga metoda) ar aduce tacut liniile gresite din
|
||||
`crsarticole` (lista de preturi) in loc de `crsarticole1` (articolele/ratele contractului) — o eroare
|
||||
mai grava decat lipsa vizibilitatii, pentru ca n-ar da nicio eroare, doar rezultat gresit.
|
||||
|
||||
**Alegerea selectiva** trebuie sa respecte aceeasi regula: dupa ce `cauta_alfa` intoarce XML-ul cu
|
||||
randurile bifate, bucla care le proceseaza trebuie sa pozitioneze cursorul sursa (`crsarticole` sau
|
||||
`crsarticole1`, dupa caz) pe `id_c`-ul corespunzator inainte de fiecare apel `do_adauga_articol`, **nu**
|
||||
sa reconstruiasca linia din campurile XML direct — altfel cele doua cai ("tot" vs. "alege") ar avea
|
||||
doua implementari diferite ale "ce inseamna sa adaug randul X", exact riscul pe care criteriul de gata
|
||||
din plan il exclude explicit ("rezultatul in `crsfactura` e identic pe cele doua cai cand selectia e
|
||||
totala").
|
||||
|
||||
**Pe comanda/avize/retur, criteriul e deja adevarat prin constructie** — `do_adauga_tot` foloseste azi
|
||||
`crsarticole`, care e cursorul lor unic; singurul de completat e contractul (cele doua cazuri de mai
|
||||
sus).
|
||||
|
||||
---
|
||||
|
||||
## 6. Interactiunea cu S4 (cautarea articolelor pe server)
|
||||
|
||||
Decizia din S4 (`plan_13...md:1879-1888`) e explicita: `crsarticole` **nu se mai incarca in masa**
|
||||
pentru facturarea libera (lista de preturi/nomenclator), inlocuita cu `combosql` pe cod/denumire care
|
||||
completeaza randul pe loc — **dar** "se pastreaza adauga tot pentru tipurile care au document sursa
|
||||
(comanda, aviz, contract) — acolo setul e marginit si incarcarea lui e legitima". Consecinta directa
|
||||
pentru meniul S4b:
|
||||
|
||||
- **Optiunile "Cauta in lista de preturi…" si "Alege din nomenclator…"** (constante peste tot, cf.
|
||||
deciziilor 16/18/20) **nu deschid un dialog de tip `cauta_alfa`** — ele sunt fatada meniului pentru
|
||||
mecanismul S4: `combosql` pe o singura linie, populata pe loc, fara incarcare in masa. In termeni de
|
||||
UI, alegerea uneia din aceste doua optiuni echivaleaza cu ce face azi `But_nou1.do_adauga` din
|
||||
prototip (`APPEND BLANK` + focus pe celula de cod/denumire) — deci **"linie noua" (sectiunea 1) si
|
||||
aceste doua optiuni de meniu ajung la acelasi gest UI**, doar ca "linie noua" il porneste direct
|
||||
(fara meniu), iar cele doua optiuni de meniu il pornesc din context (dupa ce operatorul a vazut si
|
||||
restul optiunilor sursei). Nu sunt cai de cod diferite — sunt doua puncte de intrare in acelasi
|
||||
`APPEND BLANK` + editare inline.
|
||||
- **Toate optiunile "Adauga tot din X…" / "Alege X…"** (comanda, contract, avize, retur) opereaza pe
|
||||
cursoarele bounded (`crsarticole`/`crsarticole1`) pe care **S4 le pastreaza neschimbate** — pentru
|
||||
aceste tipuri, nimic din mecanismul de cautare pe server nu se aplica; secțiunile 3-5 de mai sus
|
||||
raman valabile ca atare.
|
||||
- **Punct de atingere real intre cele doua povesti**: campul `id_pol` pe randul adaugat prin
|
||||
`combosql` (S4, cautare din nomenclator liber) — subiectul blocajului `FACT-024` documentat pe larg
|
||||
in plan (J-ter/J-quater) si in `idpol_comanda_contract.md`. S4b **nu rezolva** acest blocaj (e
|
||||
decizia de produs deschisa la J-quater, intre reteta in 4 pasi / nota pe antet / nota pe antet ca
|
||||
fallback) — doar il mosteneste: optiunea "Alege din nomenclator…" din meniul S4b va ajunge la aceeasi
|
||||
eroare `FACT-024` la salvare, indiferent de cum arata butonul, pana cand acea decizie separata se ia.
|
||||
De mentionat explicit in criteriul de gata al S4b, ca sa nu se creada ca butonul rezolva problema.
|
||||
|
||||
---
|
||||
|
||||
## 7. Ordinea de executie in pasi
|
||||
|
||||
1. **Bara de linie (`but_nou`, `but_sterge`, "detalii linie")**, mutata deasupra gridului, cu
|
||||
`Caption` explicit pe fiecare instanta (clasele `but_nou`/`but_sterge` au deja `ToolTipText`, nu au
|
||||
niciodata `Caption` — de setat pe instanta, nu de asteptat de la clasa).
|
||||
*Verificabil*: cele trei butoane sunt vizibile deasupra gridului de linii, fiecare cu text vizibil
|
||||
(nu doar iconita), pe orice tip de document deschis.
|
||||
2. **Corectarea `Do Case` din `Init`** (`ofacturare.vc2:15109-15248`): adauga `52` la ramura
|
||||
`Inlist(poDate.tip, 2, 6)` (linia 15129) astfel incat sa primeasca titlu, eliminare `cSerie`, si sa
|
||||
fie tratat identic cu 2/6 in tot restul metodei — corecteaza golul mai mare decat cel semnalat in
|
||||
plan (sectiunea 3 de mai sus). Adauga `26` la lista tipurilor care primesc `but_urmator_tot1.Visible
|
||||
= .T.` candva in pasul 3, nu aici (26 ramane azi cu titlu corect, doar fara butonul "tot").
|
||||
*Verificabil*: un document nou de tip 52 arata acelasi titlu si acelasi grid (fara `cSerie`) ca un
|
||||
document de tip 2/6.
|
||||
3. **Extinderea `do_adauga_tot`** cu ramura pe `crsarticole1` (parametru sau ramura separata dupa
|
||||
`poDate.tip`/`crsarticole1.opt_facturare`), plus vizibilitatea butonului/optiunii pe 2/6/26/52 —
|
||||
**cei doi pasi impreuna**, nu separat (vizibilitate fara continut ar aduce un buton mut; continut
|
||||
fara vizibilitate nu s-ar vedea niciodata).
|
||||
*Verificabil*: pe un contract cu `OPT_FACTURARE=3` si articole configurate, "adauga tot" produce in
|
||||
`crsfactura` exact liniile din `crsarticole1`; pe un contract cu `OPT_FACTURARE IN (1,2)`, produce
|
||||
cate o linie per rata neincasata.
|
||||
4. **Butonul unic "Adauga articole" cu `xmenu()`**, inlocuind `But_urmator_tot1` (fara caption) si
|
||||
orice buton separat de alegere — construit dinamic dupa tabelul din sectiunea 3, cu eticheta pe
|
||||
ramura de contract aleasa in functie de `crsarticole1.opt_facturare` (sectiunea 4.3).
|
||||
*Verificabil*: pe fiecare sursa, meniul deschis contine exact optiunile din tabelul sectiunii 3, in
|
||||
ordinea specificata, iar alegerea `ESC` (`xmenu()` intoarce 0) nu declanseaza nimic.
|
||||
5. **Dialogul de alegere selectiva**, pe reteta `cauta_alfa(..., tnTipReturn=1)` (sectiunea 4),
|
||||
cu mesaj explicit la zero rezultate (4.5) si populare prin `do_adauga_articol` rand cu rand (5) —
|
||||
cate o instantiere per sursa (comanda/contract-articole/contract-rate/avize/retur-linii), cu
|
||||
`tcselect`/`tcfiltru` proprii.
|
||||
*Verificabil*: pe fiecare sursa, bifarea a N randuri din M disponibile adauga exact acele N randuri
|
||||
in `crsfactura`, cu aceleasi valori ca daca ar fi fost adaugate individual; bifarea tuturor produce
|
||||
acelasi rezultat ca "adauga tot" (criteriul de gata al plan-ului, rescris verificabil).
|
||||
*Depinde de*: pasul 3 (aceeasi rutina `do_adauga_articol` trebuie sa functioneze deja pe
|
||||
`crsarticole1` inainte ca dialogul de alegere sa se poata baza pe ea).
|
||||
6. **Reconcilierea cu selectia de facturi la antet** (sectiunea 4.2, 9.3) — decizie separata,
|
||||
nu blocanta pentru pasii 1-5: daca "Alege facturile de returnat…" ramane doar la antet (cum e azi)
|
||||
sau devine posibila si din bara de butoane.
|
||||
|
||||
*Gata cand (rescris)*: pe fiecare din cele sase surse (lista de preturi, contract-articole,
|
||||
contract-rate, comanda, avize, retur), meniul "Adauga articole" ofera exact optiunile tabelate in
|
||||
sectiunea 3; pentru fiecare sursa cu "tot"/"alege" ambele prezente, o selectie totala prin dialogul de
|
||||
alegere produce in `crsfactura` acelasi set de randuri (aceleasi valori, nu doar acelasi numar) ca
|
||||
"adauga tot"; zero rezultate in orice dialog de alegere produce un mesaj care spune sursa si motivul,
|
||||
nu un dialog gol; tip 52 se comporta identic cu tip 2/6 in titlu si structura de grid.
|
||||
|
||||
---
|
||||
|
||||
## 8. Ce NU se poate testa headless
|
||||
|
||||
- **Continutul si comportamentul meniului `xmenu()`** — `Activate Screen` + `Define Popup ... Bar` e
|
||||
UI nativa Windows/VFP (shortcut popup la pozitia mouse-ului); nu exista API de citire a itemilor unui
|
||||
popup activ prin automatizare headless. Verificarea "meniul contine optiunile N" se poate face doar
|
||||
**indirect**, citind stringul `lcMeniu` construit inainte de apelul `xmenu()` (verificabil headless,
|
||||
ca text), nu popup-ul afisat efectiv.
|
||||
- **Coloanele griduri (`grd_articole`, `grd_contracte`, `grd_factura`, si viitorul grid al dialogului
|
||||
de alegere)** — capcana deja cunoscuta si confirmata pe acest proiect: sub `-A -T`
|
||||
(rulare headless) `ColumnCount=0` si `RecordSource` raman artefacte, coloanele nu se materializeaza.
|
||||
Exista un harness UI **vizibil** (nu headless) care le citeste corect — orice verificare vizuala a
|
||||
meniului/dialogului de alegere trebuie sa treaca prin acel harness, nu prin rulare `-A -T`.
|
||||
- **`cauta_alfa`/`cauta_alfa_form_plus`** — dialog modal cu `.Show(1)`, blocheaza thread-ul UI pana la
|
||||
interactiune; testarea automata ar necesita fie injectare de input (interzisa pe aceasta masina,
|
||||
partajata cu Marius — vezi memoria `masina-partajata-fara-input-real`), fie apelarea directa a
|
||||
functiilor Oracle/cursor din spatele dialogului, ocolind UI-ul (verifica *datele*, nu *dialogul*).
|
||||
- **`AMESSAGEBOX` la zero rezultate** — verificabil ca text al mesajului in cod (string literal), nu ca
|
||||
aparitie reala pe ecran, din acelasi motiv.
|
||||
- **Pozitionarea vizuala "deasupra gridului"** (`Top`/`Height` relative) — se poate verifica numeric
|
||||
(comparand `Top`-ul butoanelor cu `Top`-ul gridului in `.vc2`), dar nu si "arata bine"/aliniere
|
||||
vizuala — necesita captura de ecran prin harness-ul UI vizibil.
|
||||
|
||||
Ce **se poate** verifica headless, direct: continutul cursoarelor (`crsarticole`, `crsarticole1`,
|
||||
`crsfactura`) dupa apelul rutinelor de adaugare, apelate direct (nu prin click) cu parametri de test;
|
||||
stringul `lcMeniu` construit inainte de `xmenu()`; valorile `Visible`/`Caption`/`ToolTipText` setate pe
|
||||
obiectele din `.vc2` (proprietati statice, nu comportament de runtime).
|
||||
|
||||
---
|
||||
|
||||
## 9. Riscuri, capcane de UX, si ce ramane de decis de Marius
|
||||
|
||||
### 9.1 Regulile de formulare/griduri (`COMUN\docs\reguli_lucru.md`, punctul 7)
|
||||
|
||||
Aplicabile direct aici (citite, respectate in proiectarea de mai sus):
|
||||
- **`GO` pe `Recno()`** — orice bucla de tip `do_adauga_tot`/dialog de alegere care itereaza cursorul
|
||||
sursa (`crsarticole`/`crsarticole1`) trebuie sa retina si sa refaca pozitia (`Recno()`) dupa fiecare
|
||||
apel `do_adauga_articol`, exact cum face deja `do_adauga_tot` azi (`lnNrInregistrare = Recno()` ...
|
||||
`Go lnNrInregistrare`, `:13175,13185,13193`) — pastrat si in ramura noua pe `crsarticole1`.
|
||||
- **UX formulare/griduri** — butoanele fara `Caption` sunt azi conforme cu restul suitei (toate
|
||||
butoanele suita sunt icon-only); a le adauga `Caption` e o schimbare vizibila la nivelul intregii
|
||||
bare, nu doar a celor trei butoane noi — de discutat daca `But_renunt1`/`But_reset1` raman mute langa
|
||||
butoane cu text (inconsistenta vizuala posibila, nu doar tehnica).
|
||||
|
||||
### 9.2 Contractul are doua meniuri, nu unul (sectiunea 4.3)
|
||||
|
||||
Tabelul din plan (J) trateaza "contract" ca o singura sursa cu un singur set de optiuni. Pe cod, un
|
||||
contract e **fie** OPT_FACTURARE=3 (articole), **fie** 1/2 (rate) — niciodata ambele. Eticheta
|
||||
"Alege ratele de facturat…" din decizia explicita a lui Marius e corecta doar pe ramura a doua; pe
|
||||
prima, echivalentul e "Alege articolele din contract…". **De decis**: se pastreaza o singura eticheta
|
||||
generica ("Alege din contract…") care acopera ambele cazuri fara sa distinga in text, sau se
|
||||
diferentiaza explicit (mai clar pentru operator, dar mai mult cod de verificare a lui
|
||||
`crsarticole1.opt_facturare` inainte de a construi meniul)?
|
||||
|
||||
### 9.3 "Alege facturile de returnat…" — la antet sau la bara de butoane?
|
||||
|
||||
Azi (sectiunea 4.2), selectia facturilor sursa pentru retur se face **o singura data**, la deschiderea
|
||||
formularului de antet (`frm_date_factura.do_cauta_facturi`), inainte ca formularul de articole sa
|
||||
existe. Meniul S4b, asa cum e cerut de decizia 13, ar pune "Alege facturile de returnat…" **in bara de
|
||||
butoane a formularului de articole** — adica dupa ce `poDate.listaid` a fost deja fixat. Doua optiuni,
|
||||
de decis de Marius:
|
||||
1. Optiunea din meniul S4b **nu schimba** setul de facturi sursa — e doar un rascolitor spre inapoi,
|
||||
catre acelasi dialog de la antet, dar utilizatorul trebuie sa iasa din pasul de articole ca sa-l
|
||||
foloseasca (comportament posibil confuz: optiunea apare in meniul de articole, dar actioneaza pe alt
|
||||
ecran).
|
||||
2. Se adauga un mecanism nou: alegerea de facturi suplimentare **din formularul de articole**, care
|
||||
extinde `poDate.listaid` si reincarca aditiv `crsarticole` (`cursor_retur_document` apelat a doua
|
||||
oara, cu filtru pe facturile noi, `INSERT INTO crsarticole` in loc de `SELECT INTO`) — cere cod nou
|
||||
in VFP, dar nu in `pack_facturare` (procedura Oracle ramane aceeasi, doar apelata de mai multe ori).
|
||||
|
||||
"Alege liniile de returnat…" (per articol, din facturile deja alese) e diferita si **nu are conflict**
|
||||
cu fluxul de azi — e un dialog de alegere clasic peste `crsarticole`, ca pe comanda/avize.
|
||||
|
||||
### 9.4 `id_pol` pe rata de contract — `NULL` prin constructie, dar validat corect
|
||||
|
||||
Rata de scadentar (`crsarticole1`, ramura `OPT_FACTURARE IN (1,2)`) are **`id_pol` NULL** prin
|
||||
constructia SQL insasi (`NULL AS ID_POL`, `:2840`) — dar asta nu ajunge niciodata la `FACT-024`,
|
||||
pentru ca ratele nu trec prin `contabilizeaza_articol`, ci prin `contabilizeaza_rata` (functie separata
|
||||
in `pack_facturare`, `idpol_comanda_contract.md` §0c), care citeste nota din `CONTRACTE.ID_NOTA`. **De
|
||||
verificat inainte de implementare**: `do_adauga_articol`/`do_scrie_articole` scriu randul de rata in
|
||||
`crsfactura` cu ce anume in coloana `id_pol` (probabil `NULL`, mostenit din `poArticol`) — daca
|
||||
salvarea foloseste ramura corecta (`contabilizeaza_rata`, nu `contabilizeaza_articol`) pe baza altui
|
||||
semnal decat `id_pol` (probabil `id_ctr`/`opt_facturare` pe linie), nu pe baza lui `id_pol` fiind gol.
|
||||
Nu s-a verificat cine alege intre cele doua functii la salvare — e in afara perimetrului S4b (tine de
|
||||
scriere, nu de bara de butoane), dar merita un pointer explicit ca sa nu se presupuna gresit ca "rata
|
||||
fara `id_pol`" e acelasi caz cu "articol din nomenclator fara `id_pol`" (J-quater) — sunt cai de cod
|
||||
diferite, cu tratament diferit.
|
||||
|
||||
### 9.5 Ce ramane strict de decis de Marius
|
||||
|
||||
1. Eticheta pe contract (9.2) — generica sau diferentiata pe `OPT_FACTURARE`.
|
||||
2. Fluxul "Alege facturile de returnat…" (9.3) — raman la antet sau se adauga incarcare aditiva.
|
||||
3. Daca `But_renunt1`/`But_reset1` primesc si ele `Caption`, pentru consistenta vizuala cu bara nou-
|
||||
etichetata, sau raman icon-only (9.1).
|
||||
4. Ordinea si formularea exacta a optiunilor in `xmenu()` per sursa (tabelul din sectiunea 3 e o
|
||||
propunere, nu o decizie finala de text).
|
||||
|
||||
---
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare incheiata, fara nicio problema de context intalnita in aceasta runda — nu a fost necesara
|
||||
predarea. Toate cele noua sectiuni cerute de brief sunt completate mai sus, cu `fisier:linie` verificat
|
||||
direct pe fisierele text reale (nu `.bak`), plus SQL-ul din `PACK_FACTURARE` exportat curent
|
||||
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`). Nicio editare de
|
||||
cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun commit.
|
||||
489
docs/cercetare/s4c_discount_in_grid.md
Normal file
489
docs/cercetare/s4c_discount_in_grid.md
Normal file
@@ -0,0 +1,489 @@
|
||||
# S4c — Discountul pe linie, mutat din dialog in grid — proiectare implementabila
|
||||
|
||||
Cercetare + proiectare READ-ONLY pentru `docs\plan_13_unificare_formular_facturare.md`, `#### S4c`
|
||||
(`:2156-2190`). Nu s-a modificat niciun fisier, nu s-a rulat `git_sync.ps1`/`txt2vcx.ps1`, nu s-a dat
|
||||
commit, nu s-a scris nimic pe Oracle (numai `SELECT`, niciunul rulat de fapt — toata cercetarea a
|
||||
fost pe cod VFP). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`.
|
||||
|
||||
**Surse de adevar deja stabilite, citate ca atare** (nu se re-verifica aici):
|
||||
`docs\cercetare\discount_in_rapoarte_si_efactura.md` (verdictul principal — niciun raport/eFactura nu
|
||||
tipareste discountul, dar amandoua citesc `valdiminuatftva`/`discountftva` din cursorul de tiparire),
|
||||
`docs\cercetare\discount_verificare2.md` (structura reala a coloanelor din `grd_factura` si a
|
||||
`VANZARI_DETALII`). **`docs\cercetare\discount_pe_articol.md` NU e citat ca sursa** — task-ul mi-a
|
||||
semnalat ca are doua afirmatii gresite, corectate deja in `discount_verificare2.md` sectiunea finala
|
||||
("Ce era gresit in afirmatiile de mai sus").
|
||||
|
||||
## Verdict (10 randuri)
|
||||
|
||||
S4c e implementabila cu cod nou moderat, nu cu redesign. Contractul de calcul exista deja, complet si
|
||||
reciproc, in `frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`) si
|
||||
`frm_articol_factura.do_calculeaza_totaluri` (`:2068-2179`, delegand la functia partajata
|
||||
`calculeaza_totaluri()` din `oproceduri_facturare.prg:2258-2381`) — aceasta din urma **e deja scrisa
|
||||
generic, pe orice obiect cu proprietatile potrivite**, deci se poate rula direct pe un rand din
|
||||
`crsfactura` (via `Scatter`/`Gather Name`), fara sa mai treaca prin dialog. Tiparul de eveniment
|
||||
pentru coloane editabile de grid **exista deja in acelasi fisier**, pe alt formular din aceeasi
|
||||
familie de clase (`frm_avizare_lucrare.grd_articole.cCantitate/cPret.Text1.LostFocus`,
|
||||
`:6549-6562`) — `LostFocus` care cheama `Thisform.do_calculeaza_totaluri()`, nu `InteractiveChange`.
|
||||
**Capcana reala e alta decat pare din titlul poveste**: exista **doua cai de scriere independente**
|
||||
catre destinatii diferite, si doar una e azi corect legata. `do_scrie_articole` trimite spre Oracle
|
||||
(`pack_facturare.adauga_articol_factura`, `ofacturare.vc2:14081-14083`) discountul citit **direct
|
||||
din `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva`** — deci `VANZARI_DETALII.DISCOUNT_UNITAR`
|
||||
**e mereu corect**, indiferent de bug, pentru ca aceste campuri sunt chiar campurile editate in grid.
|
||||
Riscul e strict local, in sesiunea VFP curenta: `prelucreaza_factura` (apelata pentru tiparire/eFactura,
|
||||
`ofacturare.prg:1887`) construieste cursorul de tiparire **din acelasi `crsfactura` deja in memorie**,
|
||||
citind `valdiminuatftva`/`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva` — campuri **agregate**
|
||||
(pret-discount)*cantitate, care **nu se recalculeaza singure** cand se editeaza discountul brut. Deci
|
||||
Oracle poate avea `DISCOUNT_UNITAR` corect si totusi factura tiparita/eFactura sa arate valoarea veche,
|
||||
in aceeasi sesiune, pana la reincarcare. Solutia: coloanele noi de discount trebuie sa scrie, pe
|
||||
`LostFocus`, atat campurile brute cat si campurile agregate — vezi sectiunea 4.
|
||||
|
||||
---
|
||||
|
||||
## 1. Lantul complet al campurilor de discount, cu `fisier:linie`
|
||||
|
||||
### 1.1 Structura cursorului local `crsfactura`
|
||||
|
||||
Definit in `creeaza_facturacrs` (`COMUN\programe\ofacturare_comun.prg:1779-1782`):
|
||||
```
|
||||
1779 valdiscountctva N(20,max(gnPc,4)),valdiminuatftva N(20,max(gnPc,4)),valdiminuattva N(20,max(gnPc,4)),valdiminuatctva N(20,max(gnPc,4)),proc_Tvav N(20,4),cu_tva N(1), lot C(20) null, serie c(100) Null,;
|
||||
1782 vvaldiscountctva N(20,max(gnPVal,4)),vvaldiminuatftva N(20,max(gnPVal,4)),vvaldiminuattva N(20,max(gnPVal,4)),vvaldiminuatctva N(20,max(gnPVal,4)),id_set_fact N(20) Null,explicatie M Null,;
|
||||
```
|
||||
(`discountftva`, `discountctva`, `vdiscountftva`, `vdiscountctva`, `valdiscountftva`, `valdiscountctva`,
|
||||
`vvaldiscountftva`, `vvaldiscountctva` sunt definite pe liniile adiacente, aceeasi procedura —
|
||||
nu recitate individual, tiparul `N(20,...)` e identic).
|
||||
|
||||
**Nomenclatura confirmata pe cod** (nu doar dedusa din nume) — opt campuri distincte, trei niveluri:
|
||||
| Camp | Nivel | Moneda | Cu/fara TVA | Sens |
|
||||
|---|---|---|---|---|
|
||||
| `discountftva` | pe unitate | lei | fara TVA | discount unitar, sursa in lei |
|
||||
| `discountctva` | pe unitate | lei | cu TVA | discount unitar, derivat |
|
||||
| `vdiscountftva` | pe unitate | valuta | fara TVA | discount unitar, sursa in valuta |
|
||||
| `vdiscountctva` | pe unitate | valuta | cu TVA | discount unitar, derivat |
|
||||
| `valdiscountftva`/`valdiscountctva` | pe linie (x cantitate) | lei | ambele | discount agregat lei |
|
||||
| `vvaldiscountftva`/`vvaldiscountctva` | pe linie (x cantitate) | valuta | ambele | discount agregat valuta |
|
||||
| `valdiminuatftva`/`valdiminuatctva` | pe linie (x cantitate) | lei | ambele | **valoare neta** (pret-discount)*cant — cea tiparita/eFactura |
|
||||
| `vvaldiminuatftva`/`vvaldiminuatctva` | pe linie (x cantitate) | valuta | ambele | valoare neta in valuta |
|
||||
|
||||
### 1.2 De la tastare la `crsfactura` — calea de azi (prin dialog)
|
||||
|
||||
1. Operator tasteaza in dialogul `frm_articol_factura` (`ofacturare.vc2:1108-2659`), pe unul din 3
|
||||
perechi de campuri: `Clb_procent_discount.Text_simplu1` (`InteractiveChange`, `:2646`),
|
||||
`Clb_discount_unitar.tx_suma_nat`/`tx_suma_val` (`Valid`, `:2614/:2620`),
|
||||
`Clb_discountctva.tx_suma_nat`/`tx_suma_val` (`Valid`, `:2602/:2608`).
|
||||
2. Toate cheama `Thisform.do_calculeaza_discount(valoare, tip)` — **`frm_articol_factura`**
|
||||
(`:1874-1976`, contract detaliat in sectiunea 2) — scrie in `poArticol`:
|
||||
`discount_unitar`, `discount_unitar_val`, `discount_unitar_ctva`, `discount_unitar_ctva_val`.
|
||||
3. `do_calculeaza_discount` cheama la final `Thisform.do_calculeaza_totaluri()` (`:1974`,
|
||||
`frm_articol_factura`, `:2068-2179`) care delega la **`calculeaza_totaluri(poArticol)`**
|
||||
(`oproceduri_facturare.prg:2258-2381`, functie globala) — aceasta scrie in `poArticol`:
|
||||
`valdiminuatftva`, `valdiminuattva`, `valdiminuatctva`, `vvaldiminuatftva`, `vvaldiminuattva`,
|
||||
`vvaldiminuatctva`, plus `valdiscountftva/ctva`, `vvaldiscountftva/ctva`, `valftva/ctva/tva` etc.
|
||||
4. La inchiderea dialogului, `do_adauga_articol` (`ofacturare.vc2:12813-13167`) scrie **tot obiectul
|
||||
deja calculat** in `crsfactura`, in doua pasi:
|
||||
- `Gather Name poArticol Fields Like ... valdiminuatftva, valdiminuattva, valdiminuatctva,
|
||||
vvaldiminuatftva, vvaldiminuattva, vvaldiminuatctva ...` (`:12945-12950`) — campurile agregate,
|
||||
**deja calculate de `calculeaza_totaluri`**, nu recalculate aici.
|
||||
- `Replace ... discountftva With poArticol.discount_unitar, discountctva With
|
||||
poArticol.discount_unitar_ctva, vdiscountftva With Nvl(poArticol.discount_unitar_val,0),
|
||||
vdiscountctva With Nvl(poArticol.discount_unitar_ctva_val,0)` (`:12952-12957`) — campurile pe
|
||||
unitate, separat, pentru ca nu sunt in lista `Gather` (doar variantele `val*` agregate sunt).
|
||||
5. `do_modifica` (`:13746-13914`, editarea unei linii existente) urmeaza acelasi tipar —
|
||||
`Gather`/`Replace` cu aceleasi campuri (`:13837-13881`), dupa ce redeschide dialogul pe rand.
|
||||
6. **Cale suplimentara, deja existenta, cu bug documentat**: pe factura in valuta, coloana
|
||||
`cVdiscountftva` (`ControlSource="vdiscountftva"`, `ofacturare.vc2:12340-12345`) e editabila
|
||||
direct in grid, **fara niciun handler** — scrie `vdiscountftva` direct in `crsfactura` prin
|
||||
binding-ul standard de grid, dar **nu recalculeaza** `vvaldiminuatftva`/`vvaldiminuatctva`
|
||||
(confirmat cautat explicit, `discount_verificare2.md` punctul 3).
|
||||
|
||||
### 1.3 De la `crsfactura` la Oracle (`VANZARI_DETALII.DISCOUNT_UNITAR`)
|
||||
|
||||
`do_scrie_articole` (`ofacturare.vc2:13916-...`), la finalizarea facturii, trimite spre
|
||||
`pack_facturare.adauga_articol_factura` un singur parametru de discount, ales **direct din campurile
|
||||
pe unitate** ale randului curent din `crsfactura` (`:14081-14083`):
|
||||
```
|
||||
14081 IIF(poArt.cu_tva = 0,;
|
||||
14082 Iif(poArt.tip_valuta = 0,Alltrim(Str(poArt.discountftva,18,gnPPretV)),Alltrim(Str(poArt.vdiscountftva,18,gnPVal))), ;
|
||||
14083 IIF(poArt.tip_valuta = 0,Alltrim(Str(poArt.discountctva,18,gnPPretV)),Alltrim(Str(poArt.vdiscountctva,18,gnPVal))))
|
||||
```
|
||||
**Nu foloseste `valdiminuatftva`.** Deci Oracle primeste intotdeauna valoarea curenta din
|
||||
`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` — cele patru campuri pe care coloanele
|
||||
noi de grid le-ar edita direct. `VANZARI_DETALII.DISCOUNT_UNITAR NUMBER(22,6)` e singura coloana
|
||||
Oracle de discount (confirmat `discount_verificare2.md` punctul 4) — nu exista coloana de procent pe
|
||||
Oracle.
|
||||
|
||||
### 1.4 Inapoi, pentru tiparire/eFactura — `crsfacttemp`
|
||||
|
||||
`prelucreaza_factura` (`ofacturare_comun.prg:1055-1059`, apelata din `ofacturare.prg:1887`) **NU
|
||||
reincarca din Oracle** — primeste ca parametru **acelasi `crsfactura`** deja in memorie (cel scris la
|
||||
pasii 1.2/1.3), si construieste cursorul de tiparire prin agregare SQL locala:
|
||||
```
|
||||
1182 Sum(cantitate) As cantitate,pretftva-discountftva As pretftva,;
|
||||
1186 Sum(valdiminuatftva) As valftva,Sum(valdiminuattva) As valtva,;
|
||||
```
|
||||
(`ofacturare_comun.prg:1182-1186`, cazul comun `discount_evidentiat=0`). eFactura foloseste **acelasi
|
||||
cursor de iesire** (`xmlefactura.prg:231`, `LineExtensionAmount = valftva`, `PriceAmount = pretftva`,
|
||||
`xmlefactura.prg:935-937`, `:1043-1046`).
|
||||
|
||||
**Consecinta directa**: `pretftva-discountftva` de la 1182 citeste `discountftva` (mereu proaspat,
|
||||
pentru ca e campul editat direct), dar `Sum(valdiminuatftva)` de la 1186 citeste campul **agregat**,
|
||||
care ramane vechi daca nu a fost recalculat explicit dupa editare. Rezultat: **randul tiparit poate
|
||||
avea `pretftva` corect (net, recalculat corect din `discountftva`) dar `valftva` (valoarea liniei)
|
||||
gresit** — o discrepanta pret x cantitate ≠ valoare, vizibila chiar pe hartie, nu doar o valoare
|
||||
veche uniforma. Asta e mai grav decat "arata vechi" — arata **inconsistent**.
|
||||
|
||||
---
|
||||
|
||||
## 2. Contractul `frm_articol_factura.do_calculeaza_discount` (`ofacturare.vc2:1874-1976`)
|
||||
|
||||
**Semnatura**: `Lparameters tnValoare, tnTip` — `tnTip`: `1` = s-a modificat procentul, `2` =
|
||||
discount in lei (fara TVA daca `preturi_cu_tva=0`, cu TVA altfel), `3` = discount in valuta.
|
||||
|
||||
**Ramura principala** — `If poArticol.preturi_cu_tva = 0` (pretul de referinta e fara TVA, cazul
|
||||
uzual): sursa de adevar e `discount_unitar`(_val).
|
||||
- `tnTip=1` (procent -> valoare): daca `tip_valuta=0`,
|
||||
`discount_unitar = Round(pretftva * procent/100, gnPPretV)`, apoi **procentul se re-normalizeaza**
|
||||
din valoarea rotunjita (`:1888-1889`) — nu se pastreaza procentul brut tastat, ci cel rezultat din
|
||||
rotunjire. Daca `tip_valuta=1`: `discount_unitar_val` se calculeaza intai (`gnPVal`), apoi
|
||||
`discount_unitar = Round(discount_unitar_val * Curs / multiplicator, gnPPretV)`.
|
||||
- `tnTip=2` (lei -> procent): `discount_unitar = tnValoare` direct, procentul se deriva
|
||||
(`Round(discount_unitar/pretftva*100, 2)`).
|
||||
- `tnTip=3` (valuta -> procent + lei): `discount_unitar_val = tnValoare`, procentul deriva din
|
||||
valuta, apoi `discount_unitar = Round(discount_unitar_val * Curs / multiplicator, gnPPretV)`.
|
||||
- **Dupa `Do Case`, neconditionat**: `discount_unitar_ctva = discount_unitar + Round(discount_unitar
|
||||
* (proc_tvav-1), gnPPretV)` (`:1913-1914`) — varianta cu TVA e **mereu derivata** din cea fara TVA
|
||||
in aceasta ramura, niciodata sursa.
|
||||
- Daca `tip_valuta=1`, simetric: `discount_unitar_ctva_val` derivat din `discount_unitar_val`.
|
||||
|
||||
**Ramura alternativa** — `Else` (`preturi_cu_tva=1`, pretul de referinta e cu TVA): rolurile se
|
||||
inverseaza complet — `discount_unitar_ctva`(_val) e sursa (calculata direct din `tnValoare`/`pretctva`
|
||||
in cele 3 cazuri, simetric cu ramura de mai sus), iar `discount_unitar`(_val) **fara TVA** e derivat
|
||||
la final (`:1958`, `Round(discount_unitar_ctva / proc_tvav, gnPPretV)`).
|
||||
|
||||
**Rotunjiri**: `gnPPretV` pentru valorile unitare in lei, `gnPVal` pentru cele in valuta, `2` fix
|
||||
pentru procent — niciodata `gnPc` (precizia de linie/document) in aceasta metoda.
|
||||
|
||||
**La final, neconditionat** (`:1972-1974`): `Thisform.clb_tva_discount.Refresh()`,
|
||||
`Thisform.clb_pret_diminuat.Refresh()`, **`Thisform.do_calculeaza_totaluri()`** — deci orice apel
|
||||
recalculeaza si campurile agregate de linie, nu doar cele pe unitate.
|
||||
|
||||
**In valuta, ce ramane needitat de aceasta metoda**: campurile agregate (`valdiminuatftva` etc.) NU
|
||||
sunt scrise aici — `do_calculeaza_totaluri` (sectiunea urmatoare) le calculeaza separat din
|
||||
`discount_unitar`/`cantitate`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Starea de azi in grid — tabel
|
||||
|
||||
| Coloana | `ControlSource` | Formular | `ReadOnly` | Editabil azi | Recalculeaza la editare |
|
||||
|---|---|---|---|---|---|
|
||||
| `Column5`/`cDiscountCTva` | `discountctva` | `frm_facturare_articole` (productie), `:12311-12317` | `.T.` explicit | Nu | — |
|
||||
| `Column9`/`cVdiscountftva` | `vdiscountftva` | `frm_facturare_articole`, `:12340-12345` | nesetat (implicit `.F.`) | **Da, pe factura in valuta** | **Nu** — niciun `Valid`/`LostFocus`/`InteractiveChange` propriu, cautat explicit in `10968-15739` |
|
||||
| `Column5`/`cDiscountCTva` | `discountctva` | `frm_facturare_articole2` (prototip, neinstantiat in productie) | `.F.` explicit, `:16657` | Da (prototip) | Nesigur — prototip, nu s-a cautat handler dedicat |
|
||||
| `Column9`/`cVdiscountftva` | `vdiscountftva` | `frm_facturare_articole2` | `.F.` explicit, `:16688` | Da (prototip) | idem |
|
||||
| `Column14`/`procdisc` | `procdisc` | `frm_facturare_articole2` numai, `:16721` | nesetat | scaffold, fara scriere | **Nu are corespondent Oracle, nu are `Gather`/`Replace` nicaieri in fisier** (`discount_verificare2.md` sectiunea 5) |
|
||||
|
||||
**Excludere pe valuta** (`ofacturare.vc2:15269-15278`, in `frm_facturare_articole.Init`): cand
|
||||
`poDate.in_valuta = 0` se elimina `cVpretFtva`, `cVdiscountftva`, `cVvaldiminuatftva`; cand
|
||||
`in_valuta <> 0` se elimina `cPretFtva`, `cDiscountctva` (si simetricele lor). Deci azi, pe orice
|
||||
factura, **doar una din cele doua coloane de discount e vizibila** — cea in lei, needitabila, sau cea
|
||||
in valuta, editabila-dar-fara-recalcul. Nu exista azi nicio coloana de **procent** de discount in
|
||||
`grd_factura` in productie (doar `discountctva`/`vdiscountftva`, valori, nu procent) — pentru procent,
|
||||
azi operatorul trebuie sa deschida dialogul.
|
||||
|
||||
---
|
||||
|
||||
## 4. Proiectarea
|
||||
|
||||
### 4.1 Ce coloane se adauga/deschid
|
||||
|
||||
Nu se sterge nimic din `crsfactura` (modelul de date ramane neschimbat, conform cerintei). Se
|
||||
lucreaza cu campurile deja existente:
|
||||
|
||||
- **Coloana procent discount** (noua in grid, in ambele monede) — `ControlSource` pe un camp
|
||||
calculat, nu direct pe un camp Oracle (nu exista coloana Oracle de procent) — vezi 4.4 pentru
|
||||
optiunea recomandata.
|
||||
- **`cDiscountCTva`** (`discountctva`, lei): `Column5.ReadOnly` trece din `.T.` in `.F.` — devine
|
||||
editabila, simetric cu ce azi doar prototipul `frm_facturare_articole2` face.
|
||||
- **`cVdiscountftva`** (`vdiscountftva`, valuta): ramane editabila ca azi, dar castiga handler-ul
|
||||
care azi lipseste.
|
||||
|
||||
Se pastreaza `RemoveObject` pe valuta (`:15269-15278`) neschimbat — excluderea reciproca deja
|
||||
implementeaza cerinta "se pastreaza excluderea pe `in_valuta`".
|
||||
|
||||
### 4.2 Pe ce eveniment se cableaza calculul reciproc
|
||||
|
||||
**Recomandare: `Text1.LostFocus`, dupa tiparul deja folosit in acest fisier pentru coloane
|
||||
editabile de grid** — `frm_avizare_lucrare.grd_articole.cCantitate.Text1.LostFocus` si
|
||||
`.cPret.Text1.LostFocus` (`ofacturare.vc2:6549-6562`), ambele in aceeasi clasa de baza de grid
|
||||
(`_grdrow`, `_grd_base.vc2:445`) folosita si de `grd_factura`. Motivele, nu teoretice ci din cod:
|
||||
- **`InteractiveChange` fireste pe fiecare tasta** — ar recalcula la fiecare caracter tastat intr-un
|
||||
numar cu zecimale (comportament vazut la coloana `procent` in dialog, `:2646`, dar acolo controlul
|
||||
e un camp simplu de dialog, nu o celula de grid cu re-randare de coloane vecine la fiecare tasta —
|
||||
in grid ar fi vizibil costisitor si ar zgaltai focusul).
|
||||
`Valid`-urile din dialog (`:2602-2624`) folosesc explicit un guard `<> nSumaNatOld`/`nSumaValOld`
|
||||
ca sa nu recalculeze cand valoarea nu s-a schimbat efectiv — semnaleaza ca autorii au evitat
|
||||
deliberat recalculul pe fiecare tasta chiar si la `Valid`.
|
||||
- **`LostFocus` e tiparul deja validat pentru grid-uri de articole in acest fisier**, pe doua coloane
|
||||
numerice diferite (cantitate, pret), amandoua cu acelasi tip de nevoie (schimbarea unei valori
|
||||
declanseaza recalculul liniei si al totalului documentului).
|
||||
- Grid-ul VFP nu are `Valid` pe coloana insasi in mod uzual folosit aici — handlerele existente sunt
|
||||
pe `<Coloana>.Text1.LostFocus`, deci noile handlere trebuie sa fie
|
||||
`grd_factura.cDiscountCTva.Text1.LostFocus` si `grd_factura.cVdiscountftva.Text1.LostFocus`
|
||||
(plus coloana noua de procent, daca implementata ca `Text1` editabil).
|
||||
|
||||
### 4.3 Rutina de recalcul — reutilizare, nu reimplementare
|
||||
|
||||
`calculeaza_totaluri()` (`oproceduri_facturare.prg:2258-2381`) **e deja generica**: primeste orice
|
||||
obiect (`toArticol`) cu proprietatile `preturi_cu_tva`/`discount_unitar`/`pretftva`/`cantitate`/...
|
||||
(le adauga singura, prin `AddProperty`, daca lipsesc — `:2264-2272`, mapand numele de camp
|
||||
`crsfactura`-stil, `discountftva`/`vdiscountftva`, pe numele `poArticol`-stil,
|
||||
`discount_unitar`/`discount_unitar_val`) si scrie inapoi campurile agregate. **Poate rula direct pe
|
||||
un `Scatter Name` al randului curent din `crsfactura`**, fara sa deschida dialogul:
|
||||
```
|
||||
Select crsfactura
|
||||
Scatter Name loArt Memo
|
||||
loArt = calculeaza_totaluri(loArt)
|
||||
Gather Name loArt Memo
|
||||
```
|
||||
Aceasta acopera pasul "recalculeaza campurile agregate din discountul unitar deja stabilit"
|
||||
(`valdiminuatftva`, `valdiminuatctva`, `vvaldiminuatftva`, `vvaldiminuatctva`,
|
||||
`valdiscountftva/ctva`, `vvaldiscountftva/ctva`), **exact campurile care azi raman vechi**
|
||||
(sectiunea 1.4).
|
||||
|
||||
**Ce `calculeaza_totaluri()` NU face**: conversia reciproca procent<->valoare — aceea e logica din
|
||||
`do_calculeaza_discount` (sectiunea 2), care azi scrie in `poArticol` si actualizeaza controale de
|
||||
dialog (`Thisform.clb_*.Refresh()`) care nu exista in grid. **Recomandare**: se extrage o functie noua,
|
||||
fara referinte la `Thisform.clb_*` (partea de calcul pur, liniile `:1878-1970` minus liniile de
|
||||
`Refresh`), reutilizabila atat din dialog (daca dialogul de articol individual mai exista undeva —
|
||||
nu la S4c, dialogul dispare) cat si din handler-ul de grid — parametrizata pe rand
|
||||
(`toArticol`/`Scatter Name`) in loc de `poArticol` global. Aceasta functie noua intra la fel ca
|
||||
`calculeaza_totaluri()`, in `oproceduri_facturare.prg`, ca sa fie apelabila din handler-ul de coloana
|
||||
fara sa depinda de `poArticol`-ul dialogului disparut.
|
||||
|
||||
### 4.4 Coloana de procent — implementare recomandata
|
||||
|
||||
Nu exista coloana Oracle de procent (sectiunea 1.3) si nici coloana persistenta pe `crsfactura`
|
||||
pentru asta (spre deosebire de `discountftva` etc, care sunt in `creeaza_facturacrs`). Doua optiuni:
|
||||
1. **Adauga camp calculat, needitabil, alaturi de coloana editabila de valoare** — afiseaza procentul
|
||||
derivat (`Round(discountftva/pretftva*100,2)`), needitabil direct — evita sa se mai adauge o
|
||||
coloana Oracle noua si o cale de editare in plus (mai putin cod nou, mai putina suprafata de bug).
|
||||
2. **Adauga coloana editabila de procent, needitabila-pe-model** — ca in `frm_articol_factura`
|
||||
(`Clb_procent_discount`), scrie tot in `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva`
|
||||
prin acelasi calcul reciproc, dar camp de UI, nu de Oracle. Mai aproape de comportamentul de azi
|
||||
din dialog (operatorul poate tasta fie procentul, fie valoarea), dar cere si al treilea handler de
|
||||
`LostFocus` si inca un camp de lucru in cursorul local (`AddProperty` la `do_initializeaza_articol`,
|
||||
dupa tiparul `id_jtva_coloana`/`valdiminuatftva`, `:13666-13681`).
|
||||
|
||||
Cerinta din plan ("cele doua campuri de discount ale lui — procent si valoare unitara — devin coloane
|
||||
in grid") cere explicit **ambele campuri editabile** — deci optiunea 2 e cea care respecta litera
|
||||
cerintei; optiunea 1 e o simplificare de discutat cu Marius (vezi sectiunea 10).
|
||||
|
||||
### 4.5 Unde se cheama exact recalculul, pas cu pas (handler propus)
|
||||
|
||||
Pentru coloana `cDiscountCTva` (`discountctva`, lei, `tip=2` in nomenclatura `do_calculeaza_discount`):
|
||||
```
|
||||
PROCEDURE grd_factura.cDiscountCTva.Text1.LostFocus
|
||||
Select crsfactura
|
||||
Scatter Name loArt Memo
|
||||
* recalcul reciproc procent<->valoare, tip=2 -- functia noua din 4.3, nu do_calculeaza_discount
|
||||
loArt = recalc_discount_linie(loArt, This.Value, 2) && scrie discountftva/discountctva(_val)
|
||||
loArt = calculeaza_totaluri(loArt) && scrie valdiminuat*/vvaldiminuat*
|
||||
Gather Name loArt Memo
|
||||
Thisform.do_calculeaza_totaluri() && resumeaza totalurile documentului
|
||||
ENDPROC
|
||||
```
|
||||
Simetric pentru `cVdiscountftva` (`tip=3`) si pentru coloana de procent, daca implementata editabil
|
||||
(`tip=1`). Guard-ul `<> valoare veche` (ca la `Valid`-urile din dialog, `:2602-2624`) se pastreaza ca
|
||||
sa nu se recalculeze la simplu tab-through fara modificare.
|
||||
|
||||
---
|
||||
|
||||
## 5. Interactiunea cu discountul din politica de pret
|
||||
|
||||
**Azi**: `do_initializeaza_articol` (`ofacturare.vc2:13618` si urm.) preia
|
||||
`toArticol.discount_unitar = Nvl(toArticol.discount_unitar, 0)` (`:13630`) **din `crsarticole`**
|
||||
(cursorul de stoc/oferta, populat inainte de deschiderea dialogului — sursa SQL exacta, in afara
|
||||
`ofacturare.vc2`, ramasa necercetata si in raportul precedent). Daca politica de pret a populat deja
|
||||
un discount, acela apare **preincarcat** in campurile dialogului (`Clb_discount_unitar` etc.),
|
||||
operatorul il poate suprascrie tastand — suprascrierea intra prin acelasi `do_calculeaza_discount`
|
||||
ca orice alta tastare, fara distinctie intre "valoare din politica" si "valoare tastata manual".
|
||||
|
||||
**Dupa S4c**: nu se schimba nimic in mecanismul de preincarcare — `do_adauga_articol` inca scrie
|
||||
`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` in `crsfactura` la adaugarea liniei
|
||||
(sectiunea 1.2, pasul 4), inainte ca operatorul sa apuce sa editeze coloana de grid. Valoarea din
|
||||
politica **ramane vizibila in celula**, exact ca azi in dialog, iar editarea manuala in grid o
|
||||
suprascrie la fel — singura diferenta e ca suprascrierea se intampla acum pe `LostFocus` de celula,
|
||||
nu pe `Valid` de camp de dialog. **Nu exista azi o distinctie de tip "flag discount din politica vs.
|
||||
discount manual"** in `crsfactura` (nu am gasit un camp `discount_din_politica` sau similar) — deci
|
||||
S4c nu pierde nicio informatie care exista deja, dar nici nu castiga vreo trasabilitate noua.
|
||||
|
||||
---
|
||||
|
||||
## 6. Refacerea totalurilor documentului
|
||||
|
||||
Totalurile documentului (`Thisform.nbazaron`, `ntotalron`, `ndiscron`, variantele `*val`) se
|
||||
recalculeaza in `frm_facturare_articole.do_calculeaza_totaluri` (`ofacturare.vc2:13423-13520`) —
|
||||
**re-sumeaza direct din `crsfactura`** (si `crsfacturaset` daca exista), citind exact campurile
|
||||
agregate din sectiunea 1.1/1.4:
|
||||
```
|
||||
13456 Select Sum(Nvl(valdiminuatctva,0)) As Total,Sum(Nvl(valdiminuatftva,0)) As Baza,;
|
||||
13457 Sum(Nvl(valdiminuattva,0)) As Tva,;
|
||||
13458 Sum(Nvl(valdiscountftva,0)) As discount From crsfactura Into Cursor crstotalurifact
|
||||
```
|
||||
Simetric pentru valuta (`vvaldiminuat*`) mai jos in aceeasi procedura. **Consecinta directa pentru
|
||||
proiectare**: daca handler-ul de coloana (sectiunea 4.5) actualizeaza corect `valdiminuatftva`/
|
||||
`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva`/`valdiscountftva`/`vvaldiscountftva` pe randul
|
||||
editat **inainte** de a chema `Thisform.do_calculeaza_totaluri()`, totalurile documentului se refac
|
||||
automat, corect, din acelasi mecanism care azi refece totalurile la adaugare/stergere de linie
|
||||
(`do_adauga_articol:13160`, `do_sterge:14676`) — **nu trebuie cod nou pentru pasul de resumare**, doar
|
||||
apelul, la finalul handler-ului de coloana. `Thisform.do_calculeaza_totaluri` e deja legat prin
|
||||
`Bindevent` de `actualizeaza_total_mod` (`:15265`) — orice apel al lui declanseaza si actualizarea de
|
||||
ecran a etichetelor de total, fara cablaj suplimentar.
|
||||
|
||||
---
|
||||
|
||||
## 7. Pasi de implementare, ordonati, cu criteriu de "gata"
|
||||
|
||||
1. **Extrage functia de calcul reciproc** din `frm_articol_factura.do_calculeaza_discount`
|
||||
(`:1874-1976`), fara liniile de `Thisform.clb_*`/`Thisform.nprocent`, ca functie noua in
|
||||
`oproceduri_facturare.prg`, parametrizata pe obiect + valoare + tip (semnatura similara cu
|
||||
`calculeaza_totaluri(toArticol)`).
|
||||
*Gata cand*: pentru fiecare din cele 3 `tnTip` si ambele ramuri `preturi_cu_tva`, functia noua
|
||||
produce exact aceleasi `discount_unitar`/`discount_unitar_ctva`/`_val` ca metoda originala, testat
|
||||
pe acelasi set de intrari (procent, lei, valuta) — comparatie directa, nu doar citire de cod.
|
||||
2. **Deschide `Column5`/`cDiscountCTva` la editare** (`ReadOnly = .F.`,
|
||||
`ofacturare.vc2:12311-12317`), pastrand `RemoveObject` pe valuta neschimbat.
|
||||
*Gata cand*: pe factura in lei, celula de discount unitar cu TVA e editabila din grid (nu doar
|
||||
afisata).
|
||||
3. **Adauga handler `Text1.LostFocus` pe `cDiscountCTva` si pe `cVdiscountftva`**, dupa modelul din
|
||||
sectiunea 4.5: recalcul reciproc (pasul 1) + `calculeaza_totaluri()` (deja existent,
|
||||
`oproceduri_facturare.prg:2258`) + `Gather` + `Thisform.do_calculeaza_totaluri()`.
|
||||
*Gata cand*: editarea oricareia din cele doua coloane schimba, in acelasi moment, si
|
||||
`discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` **si**
|
||||
`valdiminuatftva`/`valdiminuatctva`/`vvaldiminuatftva`/`vvaldiminuatctva` pe randul curent,
|
||||
verificat prin `Browse`/inspectie cursor, nu doar pe ecran.
|
||||
4. **Adauga coloana de procent** (decizie 4.4 de confirmat cu Marius — recomandare: optiunea 2,
|
||||
editabila, ca sa respecte litera cerintei din plan), cu acelasi handler, `tip=1`.
|
||||
*Gata cand*: tastarea unui procent produce aceeasi valoare de discount ca tastarea valorii
|
||||
echivalente, pe ambele monede.
|
||||
5. **Verifica totalurile documentului** dupa editare in grid — nu ar trebui sa fie nevoie de cod nou
|
||||
(sectiunea 6), doar de apelul din pasul 3.
|
||||
*Gata cand*: dupa editarea discountului pe o linie, `Thisform.nbazaron`/`ntotalron` (si variantele
|
||||
`val`) se schimba imediat, fara sa fie nevoie de o alta actiune (adaugare/stergere de linie) care
|
||||
sa le forteze.
|
||||
6. **Verifica scrierea in Oracle** (`do_scrie_articole`, `:14081-14083`) — nu ar trebui sa fie nevoie
|
||||
de nicio schimbare, pentru ca citeste deja campurile pe unitate direct (sectiunea 1.3).
|
||||
*Gata cand*: `VANZARI_DETALII.DISCOUNT_UNITAR` dupa salvare = valoarea tastata in grid, pe o
|
||||
factura testata cu discount editat exclusiv din grid (fara sa fi trecut prin dialog, care oricum
|
||||
dispare la unificare).
|
||||
7. **Verifica tiparirea si eFactura** — cea mai importanta proba, cea ceruta explicit de plan.
|
||||
*Gata cand*: vezi sectiunea 8.
|
||||
|
||||
---
|
||||
|
||||
## 8. Cum se verifica — concret
|
||||
|
||||
**Pasii**, pe o factura de test (lei si separat valuta):
|
||||
1. Adauga o linie in grid (fara discount).
|
||||
2. Editeaza direct in grid coloana de discount (valoare, apoi pe alt rand procent) — verifica pe
|
||||
ecran ca celelalte coloane afisate (`cPretFtva`/`cVpretftva`, `cValdiminuatctva`/`cVvaldiminuatftva`
|
||||
daca ramase vizibile) se actualizeaza imediat.
|
||||
3. **Interogheaza direct cursorul** (nu doar ecranul) — din Command Window/breakpoint, pe randul
|
||||
editat: `? discountftva, discountctva, vdiscountftva, vdiscountctva, valdiminuatftva,
|
||||
valdiminuatctva, vvaldiminuatftva, vvaldiminuatctva` — toate opt trebuie sa reflecte editarea, nu
|
||||
doar cele patru "brute".
|
||||
4. **Finalizeaza factura** (`do_scrie_articole`) si interogheaza Oracle (schema `MARIUSM_AUTO`,
|
||||
conventia din `COMUN\docs\scripturi-migrare-db.md:36-40`, prin `goExecutor`, doar `SELECT`):
|
||||
```sql
|
||||
SELECT discount_unitar FROM vanzari_detalii WHERE id_vanzare = :id_factura AND id_articol = :id_articol;
|
||||
```
|
||||
trebuie sa fie egal cu valoarea tastata in grid.
|
||||
5. **Retipareste factura** (nu doar priveste pe ecranul de compunere — deschide efectiv raportul,
|
||||
`factura.fr2` sau echivalentul in lei/valuta) si verifica pe pagina tiparita ca `pretftva`/`valftva`
|
||||
pe linia editata reflecta discountul nou (pretul unitar net si valoarea liniei trebuie sa fie
|
||||
consistente intre ele: `valftva = pretftva * cantitate`, altfel exact bug-ul din sectiunea 1.4).
|
||||
6. **Genereaza XML-ul de eFactura** pentru aceeasi factura si verifica in fisier
|
||||
`cbc:LineExtensionAmount`/`cbc:PriceAmount` pe linia editata — trebuie sa corespunda cu pretul net
|
||||
nou, nu cu cel dinaintea editarii.
|
||||
7. Repeta 1-6 pe **factura in valuta**, ca sa acoperi ramura `vdiscountftva`/`vvaldiminuatftva`
|
||||
(ramura azi cu bug-ul confirmat).
|
||||
|
||||
---
|
||||
|
||||
## 9. Ce nu se poate testa headless
|
||||
|
||||
- **Editarea propriu-zisa a celulei de grid** (tastare in `Text1` al unei coloane, tab/enter pentru
|
||||
`LostFocus`) — headless-ul VFP (`-A -T`) nu materializeaza interactiunea de tastatura intr-un grid;
|
||||
cf. `docs\cercetare\...grid-coloane-nu-se-materializeaza-headless` (memorie de proiect) — coloanele
|
||||
de grid sunt artefacte needitabile sub `-A -T`; verificarea reala a comportamentului UI cere
|
||||
harness-ul cu UI vizibil sau un test manual asistat.
|
||||
- **Tiparirea efectiva a raportului** (`.frx`) — generarea unui PDF/preview real, nu doar constructia
|
||||
cursorului `crsfacttemp` in memorie, cere motorul de raportare VFP, care nu ruleaza util headless
|
||||
pentru verificare vizuala (se poate verifica campurile cursorului sursa, dar nu pagina tiparita).
|
||||
- **Trimiterea efectiva catre ANAF** (SPV) — se poate genera si inspecta XML-ul local, dar validarea
|
||||
reala (acceptare/respingere) cere mediul de test ANAF, in afara acestei sarcini.
|
||||
- **Comportamentul focus/tab-order al noilor coloane** in grid (ordinea de tab intre celule, daca
|
||||
`LostFocus` se declanseaza corect la navigare cu tastatura vs. mouse) — cere sesiune interactiva.
|
||||
|
||||
---
|
||||
|
||||
## 10. Riscuri si ce ramane de decis de Marius
|
||||
|
||||
- **Coloana de procent — editabila sau doar afisata** (sectiunea 4.4). Recomandare: editabila
|
||||
(optiunea 2), pentru ca respecta litera deciziei din plan ("cele doua campuri... devin coloane"), dar
|
||||
costa un handler si un camp de lucru in plus. Daca simplitatea conteaza mai mult decat paritatea cu
|
||||
dialogul vechi, optiunea 1 (doar afisaj) reduce suprafata de cod fara sa piarda functionalitate reala
|
||||
(operatorul tot poate obtine orice discount tastand valoarea).
|
||||
- **Riscul de rotunjire in lant**: `do_calculeaza_discount` are cel putin 3 rotunjiri succesive pe
|
||||
drumul procent->valoare->procent (sectiunea 2) — comportament deja existent, nu introdus de S4c, dar
|
||||
mutarea in grid (editare mai frecventa, rand cu rand, fara sa mai treaca prin "confirmare" de
|
||||
dialog) ar putea face vizibile discrepante mici de rotunjire care azi treceau neobservate. Nu e un
|
||||
motiv sa se schimbe rotunjirile (ar strica alte fluxuri), doar un risc de UX de semnalat.
|
||||
**Zero cazuri gasite in cod care sa demonstreze deja o problema** — semnalat preventiv, nu confirmat.
|
||||
- **`Gather`/`Scatter` pe rand cu campuri MEMO**: `do_adauga_articol` foloseste `Gather ... MEMO`
|
||||
(`:12945-12950`) — handler-ul nou de coloana (sectiunea 4.5) trebuie sa faca la fel
|
||||
(`Scatter Name ... Memo` / `Gather Name ... Memo`), altfel campul `explicatie` (M) s-ar putea goli
|
||||
la fiecare editare de discount — **de verificat explicit la implementare**, nu doar presupus.
|
||||
Recomand un test dedicat: editeaza discountul pe o linie cu `explicatie` populata, verifica ca
|
||||
`explicatie` ramane neschimbata dupa `LostFocus`.
|
||||
- **Coloana `procdisc` din prototip** (`frm_facturare_articole2`, `:16721`) — scaffold mort, fara
|
||||
scriere (sectiunea 3). Recomandare: nu se reutilizeaza ca atare pentru coloana noua de procent —
|
||||
se porneste curat, cu functia noua din sectiunea 4.3, nu cu acest camp neconectat.
|
||||
- **Formularul `frm_facturare_articole2`** ramane prototip separat, ne-instantiat in productie — S4c
|
||||
nu are nevoie sa il atinga, dar daca exista intentia sa devina formularul unificat, coloanele lui
|
||||
de discount (deja editabile, `:16657`/`:16688`) ar trebui auditate separat pentru acelasi bug de
|
||||
recalcul (nu verificat aici — in afara perimetrului cerut).
|
||||
|
||||
---
|
||||
|
||||
## 11. Punct deschis din S1 — `_checkbox1` vs `chkDetaliat`
|
||||
|
||||
Nu apartine povestii S4c (nu am gasit nicio legatura cu discountul pe linie sau `grd_factura`), dar
|
||||
am dat peste ambele controale pe cale laterala, in `frm_alte_date` (`ferestre_cere_date.vc2`), asa ca
|
||||
inchid ieftin: **sunt doua controale distincte, nu o duplicare de nume**.
|
||||
- `_checkbox1` e un control generic (mostenit din clasa de baza), folosit in `frm_alte_date.Init`
|
||||
(`ferestre_cere_date.vc2:3188-3202`) doar ca **reper de layout** — pozitia lui determina inaltimea
|
||||
ferestrei si offset-ul altor controale, fara logica de business proprie vizibila in acest formular.
|
||||
- `chkDetaliat` e un checkbox cu nume propriu, legat de fluxul de incasare (`actualizeaza_tipincasare`,
|
||||
`:2728-2853`) — vizibil doar cand `opt_incasat` indica un anumit tip de incasare (POS/detaliat),
|
||||
ascuns/zero altfel; pe ramura `Else` a lui `Init` (`:3187-3205`, cand alt tip de context nu implica
|
||||
incasare deloc) e **eliminat explicit** din formular (`Thisform.RemoveObject('chkDetaliat')`,
|
||||
`:3201`), spre deosebire de `_checkbox1`, care ramane (e folosit chiar pe linia urmatoare pentru
|
||||
calculul inaltimii finale, `:3202`).
|
||||
|
||||
**Concluzie**: nu e o inconsecventa de cod, sunt doua controale cu roluri diferite pe acelasi
|
||||
formular — punctul se poate inchide ca "nu e bug", cu rezerva ca n-am cercetat *de ce* `chkDetaliat`
|
||||
exista ca si `checkbox` separat de `opt_incasat` (ce reprezinta exact "detaliat") — nu era in
|
||||
perimetrul S4c si nu am aprofundat.
|
||||
|
||||
---
|
||||
|
||||
## Necunoscute ramase (mostenite din rapoartele-sursa, nu re-investigate aici)
|
||||
|
||||
- Interogarea SQL exacta care populeaza `crsarticole` cu discountul din politica de pret
|
||||
(`CRM_POLITICI_PRET_ART`) — cod in afara `ofacturare.vc2`, netrasat (mostenit din
|
||||
`discount_verificare2.md`, sectiunea "Necunoscute ramase").
|
||||
- Valorile implicite ale flag-urilor `gnEFACTURA_XML_DISC_PLISTA_LINIE`/`_ART` (mostenit din
|
||||
`discount_in_rapoarte_si_efactura.md`).
|
||||
- Comportamentul `crsfacturafinalaval` (varianta valuta a cursorului final de tiparire) — presupus
|
||||
simetric cu varianta lei, nu reverificat linie cu linie separat pentru S4c.
|
||||
429
docs/cercetare/s4d_zi_curs_reactiv.md
Normal file
429
docs/cercetare/s4d_zi_curs_reactiv.md
Normal file
@@ -0,0 +1,429 @@
|
||||
# Proiectare S4d — Data cursului valutar, numai cand are sens (reactiv)
|
||||
|
||||
Poveste: `docs\plan_13_unificare_formular_facturare.md`, `#### S4d` (decizia 15, proiectata in
|
||||
sectiunea M). Cercetare preliminara deja facuta si citata ca sursa de adevar:
|
||||
`docs\cercetare\zi_curs_validare.md`, `COMUN\docs\cercetare\valuta_si_curs.md`,
|
||||
`docs\cercetare\rec_dec42_proiectare.md`/`rec_d42_efactura.md` (decizia 42),
|
||||
`docs\cercetare\s3_portare_antet.md` (bug #16), `docs\cercetare\s4_cautare_articole_server.md`/
|
||||
`s4_puncte_deschise.md` (decizia 42, mecanismul ei exact). Read-only: nicio editare de cod, niciun
|
||||
`git_sync.ps1`/`txt2vcx.ps1`, nicio scriere pe Oracle.
|
||||
|
||||
---
|
||||
|
||||
## Verdict (rezumat)
|
||||
|
||||
Regula reactiva se poate implementa fara sa strice nimic din ce e deja demonstrat sigur: implicitul
|
||||
`poDate.zi_curs` ramane neconditionat (nu se schimba), iar cheia reactivitatii e un camp deja prezent
|
||||
in cursorul de grid — `crsfactura.tip_valuta` — verificabil cu exact acelasi tipar SQL folosit deja in
|
||||
cod (`ofacturare.prg:1656`, `:1730`: `Select Distinct ... From crsfactura Where tip_valuta = 1`).
|
||||
Formularul-prototip `frm_facturare_articole2` are deja, azi, un `Clb_zi_curs` propriu, editabil, legat
|
||||
la `poDate.zi_curs` (`ofacturare.vc2:16285-16305`) — nu trebuie inventat un control nou, ci reevaluata
|
||||
vizibilitatea celui care exista deja. Mesajul Oracle `-20005` **contine deja** data si numele valutei
|
||||
lipsa (`STRINGAGG` peste toate valutele fara curs) — decizia 42 nu schimba *continutul* mesajului, ii
|
||||
schimba **domeniul**: il restrange de la "toate valutele din listele de preturi ale utilizatorului" la
|
||||
"valuta articolului cautat", dar **doar pe varianta filtrata a cautarii** (S4 punctul 1); pe caile cu
|
||||
document sursa (comanda/aviz/contract) incarcarea in masa ramane neconditionata. Plan sectiunea M
|
||||
(`:2635-2638`) cere explicit ca la aceasta eroare campul sa **revina vizibil** — asta intra ca pas de
|
||||
implementare, nu ca optiune. Bug-ul #16 **nu e in perimetrul S4d** — e deja cercetat si legat de S2
|
||||
(bucla de reincercare din `ofacturare.prg`), cu verdict separat in `s3_portare_antet.md`; S4d doar
|
||||
**confirma** ca noua arhitectura (antet persistent, nu recreat) ii inlatura mecanismul, daca S2/S3 nu
|
||||
recreeaza formularul la eroare.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventarul controalelor de valuta si curs (`fisier:linie`)
|
||||
|
||||
Patru definitii `Clb_zi_curs` in tot `ofacturare.vc2` (control compus `clb_tx_data`, camp text legat
|
||||
la `poDate.zi_curs`), confirmate exhaustiv (`grep "ADD OBJECT 'Clb_zi_curs'"`):
|
||||
|
||||
| Formular | Definitie | Eliminat azi? | Validare la Termina | Sincronizare cu data |
|
||||
|---|---|---|---|---|
|
||||
| `frm_date_aviz` | `ofacturare.vc2:6727-6740` | Niciodata | Nu exista (fara `inainte_de_do_termin` care sa o ceara) | `Clb_dataact...LostFocus` (`:7603-7604`), `Clb_dataireg...LostFocus` (`:7610-7611`) — `Thisform.clb_zi_curs.Refresh()`, neconditionat |
|
||||
| `frm_date_aviz_lucrare` | `:7787-7801` | Niciodata | **Neconditionata**: `:8076` `Case Empty(Nvl(poDate.zi_curs,{}))` -> `:8078 This.clb_zi_curs.SetFocus()`, `plReturn=.F.` | `:8186-8187`, `:8193-8194`, neconditionat |
|
||||
| `frm_date_factura` | `:8701-8714` | **Doar** tip 8/9 (retur), `Init` `:9717-9722` (`RemoveObject`) | Conditionata: `:9484` `Case poDate.in_valuta=1 And Empty(...) And Type('thisform.clb_zi_curs.visible')<>'U'` -> `:9488 SetFocus` | `:9805-9808`, `:9824-9827`, ambele cu garda `Type(...)<>'U'` |
|
||||
| `frm_facturare_articole2` (prototip) | `:16285-16305`, camp text legat la `poDate.zi_curs` (`TEXT_SIMPLU1.ControlSource`) | **Niciodata** — `Init` (`:18988-19080`) nu contine niciun `RemoveObject('clb_zi_curs')` | Nu exista (formularul de articole nu are `inainte_de_do_termin` propriu de tip antet) | `Clb_dataact.TEXT_SIMPLU1.LostFocus` (`:19219-19222`) — acelasi tipar cu garda `Type(...)<>'U'` |
|
||||
|
||||
Alte controale de valuta/curs, relevante ca sa nu fie confundate cu `clb_zi_curs`:
|
||||
|
||||
- **`Ct_clb_valuta`** (selector de valuta document) — eliminat pe `frm_date_factura` cand
|
||||
`poDate.in_valuta=0` (`:9725-9728`), **neconditionat de tip**. Confirma ca "ascunde cand nu are sens"
|
||||
e deja un tipar folosit in aceeasi metoda, la doar 3 linii distanta de blocul retur — dovada directa
|
||||
ca autorul stia sa faca exact acest lucru, doar nu l-a aplicat si campului de curs.
|
||||
- **`lb_cursuri` + `grd_cursuri`** (eticheta si grid read-only "Curs valutar (data)") —
|
||||
`frm_facturare_articole.Init` (`:15092-15100`) si `frm_facturare_articole2.Init` (`:19000-19012`):
|
||||
```
|
||||
ofacturare.vc2:19000-19012
|
||||
If !Used('crscursuri') Or Reccount('crscursuri') = 0
|
||||
Thisform.RemoveObject('lb_cursuri')
|
||||
Thisform.RemoveObject('grd_cursuri')
|
||||
Else
|
||||
If !Empty(Nvl(poDate.zi_curs, {}))
|
||||
Thisform.lb_cursuri.Caption = "Curs valutar " + Iif(!Inlist(poDate.tip, 8, 9), "(" + Dtoc(poDate.zi_curs) + ")", "")
|
||||
...
|
||||
```
|
||||
Acesta e **cel mai apropiat precedent de "arata/ascunde in functie de continut"** din codul existent
|
||||
— dar evalueaza `crscursuri` (cursurile incarcate pentru toate valutele din listele de preturi ale
|
||||
utilizatorului), **o singura data, la `Init`**, inainte ca userul sa fi adaugat vreo linie pe grid.
|
||||
**Nu e reactiv** la adaugare/stergere de articole — e evaluat o singura data pe un cursor diferit de
|
||||
`crsfactura`. Nu se poate copia ca atare pentru S4d; poate fi reutilizat doar ca **idiom** (verificare
|
||||
`Reccount(...) > 0` care decide `RemoveObject`/reafisare), mutat pe alt cursor si alt eveniment
|
||||
(punctul 2).
|
||||
- **`poArticol.tip_valuta`** — proprietate a politicii de pret a articolului (nu a documentului),
|
||||
populata la `do_initializeaza_articol`/cautare, citita masiv in calculul de pret/discount
|
||||
(`ofacturare.vc2:1857-2288`, `frm_articol_factura`). Cand articolul e adaugat pe grid, valoarea
|
||||
ajunge in coloana `crsfactura.tip_valuta` (vezi punctul 2) — acesta e semnalul pe care se construieste
|
||||
conditia reactiva, nu `poArticol` (obiect temporar, mort dupa `Release`).
|
||||
|
||||
**Cine citeste `poDate.zi_curs` dupa formular** (relevant ca sa nu se rupa nimic la ascundere) — deja
|
||||
documentat exhaustiv in `zi_curs_validare.md` §4 si `valuta_si_curs.md` §4: cursoarele de articole
|
||||
(`cursor_preturi`/`cursor_articole_k`/`cursor_gestiune`/`cursor_lucrare`, `ofacturare.prg:266-308`),
|
||||
eticheta `lb_cursuri`, si validarile de mai sus. Nimic din acest tabel se schimba prin S4d.
|
||||
|
||||
---
|
||||
|
||||
## 2. Conditia reactiva, exact
|
||||
|
||||
**Camp folosit:** `crsfactura.tip_valuta` (N(1)), definit la creearea cursorului de grid,
|
||||
`creeaza_facturacrs` (`COMUN\programe\ofacturare_comun.prg:1776`), populat la fiecare linie adaugata
|
||||
prin `do_adauga_articol` (coloana e in lista de `Insert`/`Scatter`, `ofacturare.vc2:12950`,
|
||||
`:17258` pentru `frm_facturare_articole2`) direct din `poArticol.tip_valuta`.
|
||||
|
||||
**Verificare, cu exact acelasi tipar deja folosit in productie** (nu inventat):
|
||||
|
||||
```
|
||||
ofacturare.prg:1656, :1730 [listeaza_ofacturare]
|
||||
Select Distinct nume_val, Curs, multiplicator From crsfactura Where tip_valuta = 1
|
||||
```
|
||||
|
||||
Pentru S4d, verificarea de vizibilitate se reduce la:
|
||||
|
||||
```foxpro
|
||||
lVizibil = !Inlist(poDate.tip, 8, 9) And ;
|
||||
(poDate.in_valuta = 1 Or (Used('crsfactura') And Reccount('crsfactura', 1) > 0) )
|
||||
```
|
||||
|
||||
unde `Reccount('crsfactura', 1)` inseamna "exista macar un rand cu `tip_valuta=1`" — in practica se
|
||||
scrie ca `Calculate Cnt() To lnLinii For tip_valuta=1` sau `Select Count(*) From crsfactura Where
|
||||
tip_valuta=1 Into Array laCnt`, ca sa nu depinda de pozitia recordului curent din grid.
|
||||
|
||||
**Cand se reevalueaza — pe evenimentul deja existent de recalcul, nu pe un timer nou.** Atat
|
||||
`do_adauga_articol` (`:17124-17393`) cat si `do_sterge` (`:18619-18704`) se termina cu apelul
|
||||
**`Thisform.do_calculeaza_totaluri()`** (`:17360`, `:18687`) — acesta e deja punctul unic prin care
|
||||
formularul "stie" ca s-a schimbat compozitia liniilor (totaluri, discount pe articole etc). E locul
|
||||
natural unde se adauga si reevaluarea `clb_zi_curs`: dupa fiecare adaugare de linie **si** dupa fiecare
|
||||
stergere, `do_calculeaza_totaluri` (sau un apel adaugat imediat dupa el in cele doua metode) reface
|
||||
`lVizibil` de mai sus si seteaza `Thisform.clb_zi_curs.Visible = lVizibil`.
|
||||
|
||||
**Modificarea unei linii existente** (schimbare cantitate/pret pe o linie deja adaugata) nu schimba
|
||||
`tip_valuta` — acesta e o proprietate a politicii de pret, fixata la adaugare, nu editabila pe grid.
|
||||
Deci nu exista eveniment suplimentar de "editare linie" care sa afecteze conditia — doar adaugare si
|
||||
stergere pot muta numarul de linii cu `tip_valuta=1` de la 0 la >0 sau invers. (Verificat: nu exista
|
||||
`do_modifica` separat pe `frm_facturare_articole2` in indexul de simboluri — editarea unei linii merge
|
||||
prin acelasi `do_adauga_articol`/dialog, care oricum recheama `do_calculeaza_totaluri`.)
|
||||
|
||||
**De ce nu `poDate.in_valuta`-style (o singura evaluare la Init):** documentul poate incepe fara nicio
|
||||
linie in valuta si poate primi una la mijlocul sesiunii de facturare — exact cazul pe care unificarea
|
||||
il face posibil de rezolvat (azi antetul se inchide inainte sa existe `crsfactura`). Evaluarea trebuie
|
||||
sa fie pe eveniment, nu pe `Init`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Trecerea inapoi — ultimul articol in valuta e sters
|
||||
|
||||
**Recomandare: ascunde din nou (simetric), dar NU goli `poDate.zi_curs`.**
|
||||
|
||||
Argumente pe tiparul deja folosit, nu pe teorie:
|
||||
|
||||
- `lb_cursuri`/`grd_cursuri` (punctul 1) folosesc deja idiomul "vizibil doar cand `Reccount(...) > 0`"
|
||||
— o conditie booleana simpla, reevaluabila oricand fara efecte laterale, pentru ca `RemoveObject`/
|
||||
re-adaugare (sau `Visible=`) nu ating proprietatea de date din spate (`poDate.zi_curs`). Simetria
|
||||
(ascunde la 0, arata la >0) e deja tratata ca stare normala pentru acel control, nu ca o exceptie de
|
||||
construit.
|
||||
- `poDate.zi_curs` **nu se goleste niciodata** in tot codul existent (confirmat exhaustiv in
|
||||
`zi_curs_validare.md` §5-6) — nici la `RemoveObject` (tip 8/9), nici la ascunderea `lb_cursuri`. Deci
|
||||
ascunderea campului de curs cand ultima linie in valuta dispare **nu pierde valoarea**: daca userul
|
||||
adauga din nou o linie in valuta in aceeasi sesiune, campul reapare cu **aceeasi data** pe care o avea
|
||||
inainte de ascundere (fie cea introdusa manual, fie implicitul de la `Init`), nu resetata la azi.
|
||||
- Alternativa ("ramane vizibil o data aratat") ar introduce un tip nou de stare per-sesiune
|
||||
(`lFostVizibilCandva`) fara niciun precedent in cod si fara beneficiu clar — userul care sterge
|
||||
singura linie in valuta de pe o factura in lei nu mai are, de fapt, niciun motiv sa vada/editeze
|
||||
cursul; a lasa campul vizibil ar fi exact inconsistenta pe care decizia 15 vrea sa o elimine.
|
||||
|
||||
**Exceptie de la simetrie, ceruta de plan (sectiunea M, `:2635-2638`):** cand vine eroarea Oracle
|
||||
`-20005` cu campul ascuns, campul **trebuie adus inapoi vizibil**, indiferent de `Reccount`-ul curent —
|
||||
vezi punctul 5. Acesta e singurul caz in care regula reactiva simetrica se suspenda explicit.
|
||||
|
||||
---
|
||||
|
||||
## 4. Interactiunea cu `in_valuta` la nivel de document
|
||||
|
||||
`poDate.in_valuta` e stabilit o singura data, in `Init`, exclusiv din `tnTip`/`tnIdSet`
|
||||
(`ofacturare_comun.prg:248-250`, confirmat exhaustiv in `valuta_si_curs.md` §1) — **nu exista cale de
|
||||
cod care sa-l schimbe dupa Init**, iar controlul de alegere a valutei (`Ct_clb_valuta`) e chiar
|
||||
eliminat din formular cand `in_valuta=0` (`ofacturare.vc2:9725-9728`), deci operatorul nu are de unde
|
||||
sa-l aleaga manual. Prin urmare **intrebarea "ce se intampla daca operatorul schimba valuta documentului
|
||||
dupa ce a adaugat linii" nu se poate pune in arhitectura actuala** — nu exista control care sa permita
|
||||
acea schimbare. Documentul e in valuta sau nu inca de la alegerea tipului (`tnTip`), inainte ca vreo
|
||||
linie sa existe.
|
||||
|
||||
Pentru S4d, consecinta e simpla: `poDate.in_valuta=1` e o conditie **statica** pe toata durata sesiunii
|
||||
de facturare — cand e adevarata, `clb_zi_curs` ramane vizibil necontenit (ca azi), indiferent de ce se
|
||||
intampla pe grid; conditia reactiva descrisa la punctul 2 conteaza **doar** pentru ramura
|
||||
`in_valuta=0`. Nu exista tranzitie `in_valuta 0->1` sau `1->0` de tratat.
|
||||
|
||||
---
|
||||
|
||||
## 5. Mesajul de eroare `-20005`
|
||||
|
||||
### 5.1 Unde se ridica azi
|
||||
|
||||
`pack_facturare.verifica_cursuri_valute` (Oracle, `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:16247-16274`):
|
||||
|
||||
```sql
|
||||
16268 IF V_NUME_VALUTE IS NOT NULL THEN
|
||||
16269 RAISE_APPLICATION_ERROR(-20005,
|
||||
16270 'Nu este setat cursul din data de ' ||
|
||||
16271 to_char(V_DATA_CURS, 'DD/MM/YYYY') ||
|
||||
16272 ' pentru ' || V_NUME_VALUTE || '!');
|
||||
16273 END IF;
|
||||
```
|
||||
|
||||
`V_NUME_VALUTE` e un `STRINGAGG` (`:16252-16266`) peste **toate** valutele din
|
||||
`FACT_VPRETURI_UTILIZATOR` ale utilizatorului curent care nu au curs valabil la `V_DATA_CURS` — exclude
|
||||
doar moneda nationala. **Mesajul contine deja data si numele valutei/valutelor** — nu e un cod generic.
|
||||
Apelata neconditionat din `cursor_preturi` (`:2153`), si delegat din `cursor_contract` (jumatatea
|
||||
`crsarticole`, `s4_cautare_articole_server.md:83-85`).
|
||||
|
||||
### 5.2 Traseul pana la operator, azi
|
||||
|
||||
```
|
||||
ofacturare.prg:313-317 (identic ofacturare.prg:828-832, factureaza2)
|
||||
If lnSucces < 0
|
||||
AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
|
||||
If goExecutor.nEroare = 20005
|
||||
vizualizeaza_curs(poDate.zi_curs)
|
||||
ENDIF
|
||||
```
|
||||
|
||||
`goExecutor.oPrelucrareEroare()` (`COMUN\programe\oproceduri_comune.prg:626-639`) extrage **textul
|
||||
brut** dintre `ORA-20xxx:` si urmatorul `ORA` din mesajul Oracle — pentru erori in intervalul
|
||||
20000-20999 (cazul de aici), operatorul vede **exact** textul PL/SQL de mai sus, netrunchiat, nemodificat.
|
||||
Deci raspunsul la "mesajul spune care valuta si ce zi lipsesc" e: **da, deja o face**, azi, inainte de
|
||||
orice modificare S4d.
|
||||
|
||||
### 5.3 Ce schimba decizia 42
|
||||
|
||||
Decizia 42 (plan `:512-516`) **nu schimba textul** mesajului — schimba **domeniul de valute verificate**,
|
||||
si **doar pe varianta filtrata a cautarii** (S4 punctul 1, `cursor_preturi` chemat per-articol-cautat,
|
||||
nu la incarcarea in masa). Azi (fara decizia 42), pe orice apel al lui `cursor_preturi`,
|
||||
`V_NUME_VALUTE` poate include valute complet nelegate de articolul pe care userul tocmai il cauta —
|
||||
de exemplu userul cauta un articol in RON, dar mesajul ii spune ca lipseste cursul pentru EUR, pentru
|
||||
ca EUR apare undeva in politicile lui de pret. Dupa decizia 42, pe cautarea filtrata, verificarea se
|
||||
restrange la valuta randului adus — deci mesajul (acelasi format text) va numi, natural, **doar
|
||||
valuta relevanta pentru cautarea curenta**, nu un agregat strain.
|
||||
|
||||
**Rezerva importanta, de citit impreuna cu S4d:** decizia 42 se aplica explicit "pe varianta filtrata
|
||||
a cursoarelor" — adica pe calea noua de cautare per-articol introdusa de S4 punctul 1, folosita azi
|
||||
doar pe **ramura de lista de preturi** (`s4_cautare_articole_server.md:68-69`: "S4 se aplica curat doar
|
||||
pe ramurile de lista de preturi"). Pe **comanda / aviz / contract**, incarcarea in masa a articolelor
|
||||
(bookkeeping obligatoriu, `crsarticole` citit si scris de `do_adauga_tot`/`do_sterge`/`do_scrie_factura`)
|
||||
**ramane neschimbata**, deci `verifica_cursuri_valute` tot ruleaza neconditionat (toate valutele din
|
||||
politicile utilizatorului), la incarcarea initiala a grilei — nu doar pe articolul cautat. **Pe aceste
|
||||
tipuri, mesajul poate inca numi o valuta care n-are legatura cu documentul curent**, indiferent de
|
||||
decizia 42 — asta ramane o diferenta reala fata de "campul e ascuns pentru ca documentul nu are nevoie
|
||||
de curs", si intra la riscuri (punctul 10).
|
||||
|
||||
### 5.4 Ce se schimba prin S4d — nu textul, ci vizibilitatea campului dupa eroare
|
||||
|
||||
Cerinta explicita din plan, sectiunea M (`:2635-2638`):
|
||||
|
||||
> Ascunderea datei de curs poate lasa un document fara curs corectabil [...] utilizatorul nu mai are
|
||||
> unde sa corecteze data daca i-am ascuns campul. **Regula din M trebuie sa aduca inapoi campul in
|
||||
> exact acest caz.**
|
||||
|
||||
Implementare propusa, la punctul de interceptare deja existent:
|
||||
|
||||
```foxpro
|
||||
* ofacturare.prg:313-317 (si simetric la :828-832)
|
||||
If lnSucces < 0
|
||||
AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
|
||||
If goExecutor.nEroare = 20005
|
||||
If Type('thisform.clb_zi_curs.visible')<>'U' And !thisform.clb_zi_curs.Visible
|
||||
thisform.clb_zi_curs.Visible = .T. && aduce campul inapoi, indiferent de conditia reactiva
|
||||
Endif
|
||||
vizualizeaza_curs(poDate.zi_curs)
|
||||
ENDIF
|
||||
```
|
||||
|
||||
Nota: `thisform` aici nu e formularul de antet (deja inchis la acest punct in arhitectura veche) — e
|
||||
motivul pentru care acest fix e legat de rezultatul S3/S2 asupra bug-ului #16 (punctul 6): daca
|
||||
antetul unificat ramane deschis/persistent (nu recreat), `thisform.clb_zi_curs` exista si poate fi
|
||||
readus vizibil direct; daca arhitectura tot recreaza un formular nou dupa eroare, readucerea vizibila
|
||||
trebuie facuta in `Init`-ul noii instante, verificand acelasi semnal (`goExecutor.nEroare=20005` din
|
||||
iteratia anterioara, sau un flag explicit propagat).
|
||||
|
||||
**Textul mesajului propus de pastrat neschimbat** (deja corect): *"Nu este setat cursul din data de
|
||||
DD/MM/YYYY pentru <valuta>!"*. Nu se propune inlocuirea lui — se propune doar sa nu mai fie surprinzator
|
||||
faptul ca operatorul nu are unde sa corecteze, prin readucerea campului.
|
||||
|
||||
---
|
||||
|
||||
## 6. Verificarea daca #16 dispare
|
||||
|
||||
**Ce e #16:** `COMUN\docs\todos.txt:45` — "la revenire din formularul de curs valutar, focusul revine
|
||||
[...] pe TIP DOCUMENT, si la iesire din serie se regenereaza numar act". Citat si in plan
|
||||
`:1926-1927`, `:2665-2668`.
|
||||
|
||||
**Deja cercetat, cu verdict**, in `docs\cercetare\s3_portare_antet.md` §6 (runda 9), **inainte** de
|
||||
aceasta sesiune — S4d nu redeschide cercetarea, o **confirma si o leaga** de domeniul propriu (eroarea
|
||||
`-20005`, singurul declansator relevant pentru S4d):
|
||||
|
||||
- **#16 nu e un bug de focus.** E o bucla de reincercare (`ofacturare.prg:174-571`, `Do While
|
||||
lnRaspuns=6`) care trateaza **orice** esec Oracle la incarcarea cursorului de articole ca pe un "DA,
|
||||
utilizatorul vrea alt document" — `lnRaspuns` nu se reseteaza pe ramura de eroare
|
||||
(`ofacturare.prg:555-564`, in `Else`, niciodata atinsa cand `lnSucces<0`), deci bucla externa reintra
|
||||
automat si **recreeaza formularul de antet de la zero** (`Createobject`, `:230`). `Init`-ul noii
|
||||
instante seteaza focus neconditionat pe `ct_clb_fdoc` (`:9788-9793`) — asta e "focusul care revine pe
|
||||
tip document" — si `LostFocus`-ul care urmeaza cheama neconditionat `clb_serie_act`, care aloca un
|
||||
numar nou (`serii_numere.vc2:122-127`) — asta e "regenerarea numarului".
|
||||
- **Cauza reala e in `ofacturare.prg` (bucla de emitere, cod comun suitei), nu in formularul de antet.**
|
||||
`s3_portare_antet.md:337-350` e explicit: portarea antetului (S3) NU rezolva automat #16 — arhitectura
|
||||
unificata (un singur `Init` persistent, antetul nu mai e un obiect separat distrus/recreat de apelant)
|
||||
**are sansa reala** sa-l elimine ca efect secundar, **dar numai daca implementarea nu recreaza
|
||||
formularul intreg la reincercare dupa o eroare Oracle** — ceea ce reproduce bugul identic, doar mutat.
|
||||
Verdictul e legat explicit de **S2**, nu de S3/S4d: bucla traieste in procedura de emitere, nu in
|
||||
formular.
|
||||
|
||||
**Raspunsul specific S4d, cerut de plan** ("Verifica in acelasi timp daca #16 dispare — vezi M"):
|
||||
**da, S4d confirma exact scenariul care declanseaza #16** — eroarea `-20005` de la
|
||||
`verifica_cursuri_valute` e unul dintre cazurile `lnSucces<0` care intra pe ramura problematica a
|
||||
buclei (`vizualizeaza_curs(poDate.zi_curs)` la `:315`, chiar linia care deschide `frm_curs`, exact
|
||||
recuperarea citata in reclamatia originala). Deci daca S2/S3 rezolva #16 (antet persistent, fara
|
||||
recreare), **S4d beneficiaza direct** — dupa `-20005`, userul revine in acelasi antet unificat
|
||||
(nu unul nou), campul `clb_zi_curs` readus vizibil (punctul 5.4) ramane exact acolo unde a fost adus
|
||||
inapoi, fara sarituri de focus si fara renumerotare. Daca S2/S3 NU rezolva #16 (recreare inca prezenta),
|
||||
S4d **nu il agraveaza si nu il repara** — comportamentul ramane identic cu azi pe acest punct, dar
|
||||
readucerea campului vizibil (5.4) trebuie facuta in `Init`-ul noii instante, nu presupusa mostenita.
|
||||
|
||||
**Concluzie:** #16 **nu e in perimetrul de implementare al S4d** (e S2, per plan `:1944`), dar S4d
|
||||
**depinde de rezultatul lui** pentru UX-ul complet al recuperarii de eroare (5.4) — de marcat explicit
|
||||
ca dependenta la implementare, nu de reinvestigat.
|
||||
|
||||
---
|
||||
|
||||
## 7. Pasi de implementare, ordonati
|
||||
|
||||
Fiecare pas lasa suita functionala; calea veche (`frm_date_factura`/`frm_facturare_articole2` cum sunt
|
||||
azi) nu se sterge in etapa I.
|
||||
|
||||
1. **Adauga functia de evaluare a conditiei reactive** pe formularul unificat (metoda noua, de ex.
|
||||
`do_actualizeaza_vizibilitate_curs`), care calculeaza `lVizibil` conform formulei din punctul 2 si
|
||||
seteaza `Visible` pe controlul `clb_zi_curs` mostenit din prototipul `frm_facturare_articole2`
|
||||
(`ofacturare.vc2:16285-16305`) — **nu un control nou**, cel existent, ale carui evenimente de
|
||||
sincronizare (`Clb_dataact...LostFocus`, `:19219-19222`) raman neschimbate (deja garda pe
|
||||
`Type(...)<>'U'`, deci tolereaza si eliminare, nu doar `Visible=.F.`).
|
||||
*Gata cand:* metoda exista, se poate apela manual (fara UI), si intoarce `.T.`/`.F.` corect pe cele
|
||||
patru combinatii din punctul 8 (tip retur / nu, `in_valuta` 0/1, cu/fara linie `tip_valuta=1`).
|
||||
2. **Cheama metoda din pasul 1 la sfarsitul `do_adauga_articol` si `do_sterge`**, imediat dupa (sau in)
|
||||
`Thisform.do_calculeaza_totaluri()` (`:17360`, `:18687` in prototip — liniile echivalente in
|
||||
formularul unificat, dupa portarea din S3/S4).
|
||||
*Gata cand:* adaugarea unei linii cu `tip_valuta=1` pe o factura in lei fara alte linii de acest fel
|
||||
face campul vizibil imediat, fara Refresh manual; stergerea ultimei asemenea linii il ascunde din nou
|
||||
(simetric, punctul 3), fara sa goleasca `poDate.zi_curs`.
|
||||
3. **Verifica initializarea la deschiderea formularului** (cazul `in_valuta=1` sau document reincarcat
|
||||
cu linii deja existente, ex. la editare factura emisa) — `Init`-ul unificat trebuie sa apeleze aceeasi
|
||||
metoda o data, dupa ce `crsfactura` e populat, nu doar sa se bazeze pe evenimentele de adaugare/
|
||||
stergere (care nu ruleaza la incarcarea initiala a unui document existent).
|
||||
*Gata cand:* deschiderea unei facturi existente (in lei, cu linii in valuta deja salvate) arata
|
||||
campul corect de la primul `Show()`, fara sa fie nevoie de o adaugare/stergere care sa-l declanseze.
|
||||
4. **Elimina campul neconditionat pe retur (tip 8/9)**, ca azi — verifica ca formula din pasul 1 include
|
||||
deja `!Inlist(poDate.tip,8,9)` inaintea oricarei alte conditii, ca sa nu-l readuca vizibil din greseala
|
||||
printr-o linie in valuta pe un retur (desi returul nu foloseste `zi_curs`, vezi `zi_curs_validare.md`
|
||||
§4c/§6 — comportamentul trebuie sa ramana identic azi, nu doar "fara efect").
|
||||
*Gata cand:* pe orice tip 8/9, campul e absent indiferent de continutul grilei.
|
||||
5. **Readu campul vizibil la eroarea `-20005`** (punctul 5.4) — modifica ramura `goExecutor.nEroare=20005`
|
||||
din bucla de emitere (`ofacturare.prg:313-317`/`:828-832`, sau echivalentul ei in noua arhitectura
|
||||
integrata cu formularul unificat, cf. S3 pasul 6) ca sa forteze `Visible=.T.` pe `clb_zi_curs` inainte
|
||||
de a deschide `frm_curs`.
|
||||
*Gata cand:* pe o factura in lei cu campul ascuns, o eroare `-20005` simulata (curs lipsa de test)
|
||||
face campul vizibil imediat dupa mesaj, inainte sau odata cu deschiderea `frm_curs`.
|
||||
6. **Coordoneaza cu decizia 42 (S4 punctul 1)**: cand acea poveste implementeaza restrangerea
|
||||
`verifica_cursuri_valute` la valuta articolului cautat, verifica ca textul afisat prin
|
||||
`oPrelucrareEroare()` (punctul 5.1-5.2, neschimbat) tot numeste corect valuta si data — nu necesita
|
||||
modificare pe partea VFP, doar confirmare ca noul parametru Oracle e transmis corect din calea de
|
||||
cautare filtrata.
|
||||
*Gata cand:* pe cautarea filtrata (dupa decizia 42), mesajul `-20005` numeste doar valuta cautata, nu
|
||||
un agregat.
|
||||
7. **Regresie pe formularul vechi**: confirma ca niciuna din modificarile 1-6 nu atinge
|
||||
`frm_date_factura`/`frm_facturare_articole` (calea veche, ramasa in productie) — toate modificarile
|
||||
se fac pe clasele formularului unificat / prototip, nu pe cele originale.
|
||||
*Gata cand:* `svn diff`/`git diff` arata modificari doar in clasele noii cai.
|
||||
|
||||
---
|
||||
|
||||
## 8. Cum se verifica
|
||||
|
||||
Pe probele din plan (`:2243-2246`), plus cazurile suplimentare care rezulta din punctele 2-6:
|
||||
|
||||
1. **Factura in lei, fara articole in valuta** — `clb_zi_curs` nu apare la deschidere; documentul se
|
||||
emite corect (acelasi rezultat `VANZARI`/`ACT`/`RUL` ca azi, criteriul din S3 §7).
|
||||
2. **Aceeasi factura, dupa adaugarea unui articol cu pret in valuta** — campul apare imediat, cu data
|
||||
implicita deja completata (data documentului, de la `Init`/`Reset`, nemodificata).
|
||||
3. **Stergerea articolului din pasul 2** (singurul cu `tip_valuta=1`) — campul dispare din nou; valoarea
|
||||
din `poDate.zi_curs` ramane cea introdusa/implicita (verificabil citind proprietatea direct, nu doar
|
||||
vizual).
|
||||
4. **Readaugarea unui alt articol in valuta, in aceeasi sesiune** — campul reapare cu **aceeasi** data
|
||||
ca la pasul 2, nu resetata.
|
||||
5. **Factura in valuta (`in_valuta=1`)** — campul apare mereu, indiferent de continutul grilei, exact ca
|
||||
azi (fara regresie pe cazul deja functional).
|
||||
6. **Retur (tip 8/9)** — campul absent indiferent de linii; document salvat corect (cf. precedent deja
|
||||
existent).
|
||||
7. **`-20005` cu campul ascuns** (factura in lei, curs de test lipsa pentru o valuta din politicile
|
||||
utilizatorului) — mesajul numeste valuta si data lipsa (deja azi); campul devine vizibil dupa eroare.
|
||||
8. **Editare factura emisa** (cf. #6, formular deja in lucru separat) — deschiderea unei facturi in lei
|
||||
cu linii in valuta deja salvate arata campul corect de la primul `Show()` (pasul 3 de implementare).
|
||||
9. **Dupa decizia 42** — cautarea filtrata a unui articol cu curs lipsa arata mesaj cu o singura valuta
|
||||
(a articolului cautat), nu un agregat.
|
||||
|
||||
---
|
||||
|
||||
## 9. Ce nu se poate testa headless
|
||||
|
||||
- **Focusul si secventa reala `SetFocus`/`LostFocus`/`Visible=`** pe controale UI reale — capcana deja
|
||||
cunoscuta (`grid-coloane-nu-se-materializeaza-headless`, memorie de proiect): sub harness `-A -T`,
|
||||
proprietatile de grid/vizibilitate nu se materializeaza identic cu UI vizibil. Verificarea
|
||||
vizibilitatii reactive (punctele 2-3) trebuie facuta fie prin verificare directa a proprietatii
|
||||
`.Visible` dupa apelul metodei (fara `Show()`), fie cu `vfp_ui_harness.ps1`, UI vizibil.
|
||||
- **Eroarea Oracle `-20005` reprodusa real** — necesita fie date de test cu un curs lipsa garantat pe o
|
||||
fereastra de date controlata (manipulare de date, nu de UI), fie mock pe `goExecutor` care simuleaza
|
||||
`nEroare=20005` fara conexiune reala — niciuna verificata ca exista deja in suita de teste.
|
||||
- **Deschiderea modala a `frm_curs`** (`vizualizeaza_curs`) — `Show(1)` modal, aceeasi limitare generala
|
||||
a testarii headless pe formulare modale.
|
||||
- **Bug #16 propriu-zis** (secventa reala de focus dupa recreare de formular) — deja marcat netestabil
|
||||
headless in `s3_portare_antet.md` §8; S4d nu adauga o cale noua de testare, doar depinde de acelasi
|
||||
rezultat.
|
||||
|
||||
---
|
||||
|
||||
## 10. Riscuri si de decis de Marius
|
||||
|
||||
1. **Incarcarea in masa pe comanda/aviz/contract ramane neconditionata dupa decizia 42** (punctul 5.3).
|
||||
Pe aceste tipuri, `-20005` poate inca numi o valuta nelegata de documentul curent, chiar daca `S4d`
|
||||
ascunde corect campul pe baza continutului grilei. **Recomandare:** de discutat daca extinderea
|
||||
restrangerii decizia-42-style merita si pe incarcarea in masa (poveste separata, posibil in S4 sau
|
||||
intr-o continuare a deciziei 42), sau se accepta ca diferenta cunoscuta, documentata aici.
|
||||
2. **Cine seteaza `Visible=.T.` la `-20005` cand antetul ar fi fost recreat** (daca #16 nu se rezolva
|
||||
complet in S2/S3 pana la implementarea S4d) — punctul 5.4/6 presupune `thisform` = acelasi formular
|
||||
persistent; daca arhitectura finala tot recreaza o instanta, logica trebuie mutata in `Init`, cu un
|
||||
semnal explicit propagat (ex. proprietate `poDate.lCursLipsa` sau echivalent) ca sa stie noua instanta
|
||||
sa arate campul. **Recomandare:** de tratat ca parte a pasului 6 din `s3_portare_antet.md` (integrarea
|
||||
buclei de emitere), nu izolat in S4d.
|
||||
3. **`frm_date_aviz`/`frm_date_aviz_lucrare` (tipurile 27, 30) nu intra in perimetrul imediat** — daca
|
||||
formularul unificat le preia mai tarziu, validarea neconditionata de la `:8076` trebuie aliniata la
|
||||
aceeasi regula reactiva (azi ramane necondiționata, per `zi_curs_validare.md` §6). **Recomandare:** nu
|
||||
se atinge acum; se trateaza cand/daca acele tipuri intra in unificare.
|
||||
4. **Simetria "ascunde la 0 linii" (punctul 3) e o recomandare, nu o certitudine ceruta explicit de
|
||||
plan** — planul cere doar "apare cand...", nu specifica explicit comportamentul la disparitia ultimei
|
||||
linii. **Recomandare:** simetria propusa aici (argumentata pe tiparul `lb_cursuri`), dar de confirmat
|
||||
cu Marius inainte de implementare, pentru ca schimba usor experienta (campul "clipeste" la
|
||||
adaugare/stergere repetata a aceleiasi linii).
|
||||
444
docs/cercetare/s4e_lista_preturi_pe_sursa.md
Normal file
444
docs/cercetare/s4e_lista_preturi_pe_sursa.md
Normal file
@@ -0,0 +1,444 @@
|
||||
# S4e — Lista de preturi disponibila si pe factura din comanda
|
||||
|
||||
Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara
|
||||
scriere Oracle — numai `SELECT`), pentru povestea **S4e** din
|
||||
`docs\plan_13_unificare_formular_facturare.md:2249-2267` (decizia 16, text integral in sectiunea J
|
||||
a planului). Nu se ating `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`
|
||||
(perimetrul altei sarcini in lucru; `COMUN\programe\ofacturare_comun.prg` si
|
||||
`COMUN\programe\ofacturare.prg` sunt citite, nu editate). Decizia 29 (stergerea unei linii din comanda
|
||||
ramane fara protectie) nu se reargumenteaza.
|
||||
|
||||
**Status: cercetare incheiata.**
|
||||
|
||||
---
|
||||
|
||||
## Verdict (esenta, pentru cine nu citeste tot)
|
||||
|
||||
**Reteta literal citata in decizia 16 — `APPEND FROM` la `ofacturare.prg:454-473`, care lipeste
|
||||
lista de preturi peste `crsarticole` — nu trebuie generalizata pe comanda ca atare. E nesigura acolo,
|
||||
pe cod, nu doar teoretic.** `crsarticole` e simultan grid-sursa **si** registrul de cantitate ramasa
|
||||
citit/scris de `do_sterge` si `do_scrie_factura` (decizia 39, S4 punctul 2). Doua motive concrete,
|
||||
verificate pe cod si pe SQL exportat:
|
||||
|
||||
1. **Coliziune de `id_c`.** Fiecare procedura `cursor_*` din `PACK_FACTURARE` numeroteaza `id_c` cu
|
||||
`ROWNUM`, pornind de la 1, **independent** de orice alta executie. `cursor_comanda` (liniile
|
||||
comenzii) si `cursor_preturi` (lista de preturi) ar produce, executate separat si apoi lipite
|
||||
prin `APPEND FROM`, doua seturi de `id_c` care se suprapun (1, 2, 3…). `do_sterge` potriveste
|
||||
randul de sters in `crsarticole` **prin `id_c`** (`ofacturare.vc2:14652-14655`,
|
||||
`:14658-14659`) — o coliziune ar face ca stergerea unei linii libere sa modifice cantitatea
|
||||
ramasa a unei linii de comanda complet diferite (sau invers), tacut, fara eroare.
|
||||
Codebase-ul insusi cunoaste acest risc: `cursor_contract` (`PACK_FACTURARE:2722`,
|
||||
`SELECT rownum - 10000 as id_c ...`) foloseste deliberat un offset ca sa nu se suprapuna cu
|
||||
spatiul de `id_c` al celuilalt cursor din acelasi apel.
|
||||
2. **Poluarea registrului.** `do_scrie_factura` face `Calculate Sum(cantitate) To lnCantitateRamasa`
|
||||
peste **tot** `crsarticole` ca sa decida daca se ofera inchiderea automata a comenzii
|
||||
(`ofacturare.vc2:14332-14338`). Coloana `cantitate` din `cursor_preturi` **nu inseamna "ramas de
|
||||
facturat"** — inseamna **stoc disponibil** (`NVL(C.CANTITATE,0)`, derivat din miscari de stoc,
|
||||
`PACK_FACTURARE:2276-2280` si similar pe fiecare ramura). Amestecarea celor doua ar face ca suma
|
||||
folosita pentru decizia de inchidere sa includa cantitati de stoc fara nicio legatura cu ce a mai
|
||||
ramas de facturat din comanda — decizia de inchidere automata ar deveni gresita, tacut.
|
||||
|
||||
**De ce merge pe contract fara aceste probleme**: pe contract, lista de preturi **nu intra niciodata
|
||||
in acelasi cursor** cu articolele contractului. `cursor_contract` intoarce **doua** cursoare Oracle
|
||||
separate (`V_CURSOR` = `cursor_preturi`, mapat pe `crsarticole`; `V_CURSOR2` = articolele/ratele
|
||||
contractului, mapat pe `crsarticole1`, cu `id_c` offset `-10000`) si **niciun `do_scrie_factura`
|
||||
nu calculeaza `Sum(cantitate)` pe tipurile de contract** (2,6,26,52) — acel `Do Case` nu are ramura
|
||||
pentru ele, cad in `Otherwise`, fara nicio logica de inchidere automata. Contractul "merge" pentru ca
|
||||
lista de preturi traieste azi acolo unde nu exista niciun registru de citit.
|
||||
|
||||
**Solutia curata pentru comanda, verificata ca fezabila pe cod**: liniile libere **nu intra deloc in
|
||||
`crsarticole`**. Mecanismul de adaugare a lor e cel construit de **S4** — cautare filtrata pe server
|
||||
(`cursor_preturi` cu parametru de filtru, `docs\cercetare\s4_cautare_articole_server.md`, punctul 4)
|
||||
legata printr-un `combosql`/`APPEND BLANK` **direct pe `crsfactura`** (exact tiparul deja existent in
|
||||
`frm_facturare_articole2.But_nou1.do_adauga`, `ofacturare.vc2:17118-17122`: `Select crsFactura /
|
||||
APPEND BLANK`), nu prin `do_adauga_articol`/`Scatter` dintr-un cursor sursa. Fiindca linia nu vine
|
||||
niciodata dintr-un rand real al lui `crsarticole`, `crsfactura.id_c` ramane la valoarea implicita a
|
||||
campului (`N(20)`, fara `Null`, deci `0` la `APPEND BLANK`) — **valoare pe care Oracle nu o produce
|
||||
niciodata** (`ROWNUM` porneste de la 1), deci `do_sterge`-ul de azi (`For id_c = poArticol.id_c`) e
|
||||
deja, prin constructie, un no-op sigur pe o linie libera, **fara nicio modificare de cod in
|
||||
`do_sterge`**. Precedentul exista deja in acelasi fisier: linia de discount adaugata la
|
||||
`ofacturare.vc2:14531-14537` (`Append Blank` pe `crsfactura`, fara `id_c`) foloseste exact acest
|
||||
tipar azi, in productie.
|
||||
|
||||
Ramane un gol real de proiectat, nu de presupus rezolvat: **validarea de cantitate/stoc pe o linie
|
||||
libera** nu se poate sprijini pe `do_verifica_articol` (cuplat de `crsarticole`) — vezi sectiunea 6.
|
||||
|
||||
---
|
||||
|
||||
## 0. Ce e deja stabilit (nu se reinvestigheaza)
|
||||
|
||||
- Optiunea "Cauta in lista de preturi…" intra in meniul de adaugare proiectat la S4b
|
||||
(`docs\cercetare\s4b_bara_butoane_meniu.md`) — S4e ii da un element de meniu, nu un buton propriu.
|
||||
- **Decizia 29**: stergerea unei linii venite din comanda ramane FARA protectie — se sterge ca
|
||||
oricare alta, comanda ramane facturata partial. Nu se reia.
|
||||
- **Legatura cu S4 punctul 2 (decizia 39)**: `crsarticole` e si registru al cantitatii ramase de
|
||||
facturat. La momentul scrierii acestui raport, `docs\cercetare\s4_punct2_registru_cantitate_ramasa.md`
|
||||
e **in lucru** (sectiuni "IN LUCRU", fara concluzie de proiectare inca). Acest raport **nu asteapta**
|
||||
acea proiectare: dovezile de mai jos (id_c, `Calculate Sum`) sunt citite direct pe codul actual, nu
|
||||
presupuse din raportul in lucru. Presupunerea facuta explicit aici: **designul propus (liniile
|
||||
libere nu intra in `crsarticole`) e compatibil cu orice varianta de decuplare a registrului pe care
|
||||
S4 punctul 2 ar alege-o** — pentru ca liniile libere nu ating deloc cursorul/mecanismul pe care
|
||||
punctul 2 il decupleaza. Daca punctul 2 alege sa mute registrul intr-un cursor complet nou (nu
|
||||
`crsarticole`), concluzia ramane aceeasi cu o singura schimbare de nume.
|
||||
- **S4 (`docs\cercetare\s4_cautare_articole_server.md`) a decis deja**, pe cod, ca pentru
|
||||
comanda/aviz (3,4,21,25,28,42,47) `crsarticole` **ramane incarcat in masa**, neschimbat — punctul 7
|
||||
al acelui raport. Aceasta decizie priveste **selectia articolelor comenzii insesi** (raman pe
|
||||
`grd_articole`/`crsarticole`/`do_adauga_tot`, neschimbate). **Nu exclude** mecanismul S4e: cautarea
|
||||
filtrata pe server construita de S4 (`cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`) e exact
|
||||
ce S4e reutilizeaza pentru liniile **libere**, un canal separat, care nu inlocuieste si nu atinge
|
||||
`grd_articole`.
|
||||
|
||||
## 1. Ce face azi calea de copiere (`ofacturare.prg:454-473`)
|
||||
|
||||
Cod complet (`COMUN\programe\ofacturare.prg:454-473`):
|
||||
|
||||
```
|
||||
ELSE
|
||||
IF m.llCopiere
|
||||
* Modificare sau copiere factura
|
||||
* Nu adaug automat articolele din factura originala, ca sa dau posibilitate de modificare a cantitatilor, pretului, explicatiei
|
||||
* ofrmdetaliifactura.do_adauga_tot()
|
||||
|
||||
* Sterg din cursorul cu articole inregistrarile adaugate in factura
|
||||
*DELETE FROM crsArticole
|
||||
|
||||
* Adaug in lista articolelor cursorul cu lista de preturi ca sa pot adauga si alte articole in factura copiata/modificata
|
||||
lcSqlCursor = [{call pack_facturare.cursor_preturi(?poDate.zi_curs,?poDate.tip,?poDate.id_valuta,] + ;
|
||||
[?poDate.id_gestiune_init,?gnLuna,?gnAn,?gnIdUtil,?gnIdSucursala)}]
|
||||
|
||||
lcCursorTemp = [crsArticoleTemp]
|
||||
llSucces = goExecutor.oExecuta(lcSqlCursor, lcCursorTemp)
|
||||
IF m.llSucces
|
||||
SELECT crsArticole
|
||||
APPEND FROM DBF(m.lcCursorTemp)
|
||||
ENDIF
|
||||
USE IN (SELECT(m.lcCursorTemp))
|
||||
ENDIF && m.llCopiere
|
||||
```
|
||||
|
||||
Pas cu pas:
|
||||
|
||||
1. Se executa `pack_facturare.cursor_preturi(...)` intr-un cursor temporar separat, `crsArticoleTemp`
|
||||
(nu direct in `crsarticole`).
|
||||
2. `SELECT crsArticole` + `APPEND FROM DBF(lcCursorTemp)` — VFP potriveste coloanele **dupa nume**;
|
||||
coloanele care exista in `crsArticoleTemp` dar nu in `crsArticole` (sau invers) sunt ignorate
|
||||
tacut de `APPEND FROM` (nu da eroare pentru coloane lipsa pe o parte sau alta — completeaza cu
|
||||
valoarea implicita a campului pe partea destinatie). Cele doua cursoare au aceeasi structura de
|
||||
baza (`creeaza_facturacrs`/coloanele standard `cursor_facturare`), deci in practica maparea e
|
||||
completa.
|
||||
3. `id_c` din `crsArticoleTemp` (numerotat `ROWNUM` de Oracle, incepand de la 1, independent de
|
||||
executia anterioara) se copiaza **ca atare** in `crsArticole` — **nu se renumeroteaza**.
|
||||
4. `crsArticoleTemp` se inchide (`USE IN`); rezultatul ramane doar in `crsArticole`, care de acum
|
||||
contine doua seturi de randuri provenite din doua interogari Oracle diferite, cu spatii de `id_c`
|
||||
care se pot suprapune.
|
||||
|
||||
**De ce e sigur aici, dar nu neaparat pe comanda**: aceasta ramura ruleaza **doar** cand
|
||||
`m.llCopiere` e adevarat, si cand `llCopiere` e adevarat, cursorul de baza `crsArticole` **nu mai
|
||||
vine din `cursor_comanda`** — Do Case-ul de la `ofacturare.prg:266-308` verifica `Case m.llCopiere`
|
||||
**primul**, inaintea oricarei ramuri pe `tnTip`, deci pentru copiere cursorul de baza e intotdeauna
|
||||
`cursor_retur_document` (documentul copiat insusi), indiferent de tipul original. Un document copiat
|
||||
nu (mai) e o "comanda" (`poDate.tip` dupa copiere nu e niciodata `3`), deci nici `do_scrie_factura`
|
||||
(care cere `poDate.Tip = 4` sau `Inlist(poDate.Tip,3,21,25,28,42,47)` pentru logica de inchidere
|
||||
automata), nici `do_sterge` (aceleasi conditii de tip) nu citesc/scriu `crsarticole` ca registru pe
|
||||
calea de copiere — **coliziunea de `id_c` exista tehnic si aici, dar nu are efect observabil**,
|
||||
pentru ca nimic nu mai citeste cursorul ca registru pe acest `poDate.tip`. Aplicarea aceleiasi tehnici
|
||||
pe un document de tip 3 (unde registrul chiar e citit) ar expune exact coliziunea descrisa in verdict.
|
||||
|
||||
## 2. De ce merge azi pe contract si nu pe comanda
|
||||
|
||||
**Pe contract, lista de preturi si articolele contractului sunt doua cursoare separate de la bun
|
||||
inceput, populate de o singura procedura Oracle cu doua `OUT REF CURSOR`-uri**
|
||||
(`PACK_FACTURARE:2646-2950`, `cursor_contract`):
|
||||
|
||||
```
|
||||
PROCEDURE cursor_contract(..., V_CURSOR OUT cursor_facturare, V_CURSOR2 OUT cursor_facturare) IS
|
||||
...
|
||||
OPEN V_CURSOR2 FOR
|
||||
...
|
||||
SELECT rownum - 10000 as id_c, id_ctr, id_articol, id_rata, ... , opt_facturare
|
||||
FROM (... CTR_ARTICOLE OPT_FACTURARE=3 UNION ALL ... CTR_SCADENTAR OPT_FACTURARE IN (1,2) ...)
|
||||
ORDER BY data, numar, data_rata, denumire;
|
||||
|
||||
pack_facturare.cursor_preturi(V_DATA_CURS, V_TIP, V_ID_VALUTA, V_ID_GESTIUNE_INIT, V_LUNA, V_AN,
|
||||
V_ID_UTIL, V_ID_SUCURSALA, V_CURSOR);
|
||||
END cursor_contract;
|
||||
```
|
||||
|
||||
`V_CURSOR` (= `crsarticole`, mapat in VFP) **este** rezultatul unui apel intern la `cursor_preturi` —
|
||||
lista de preturi completa, exact ca pe un document liber de tip 1/5/7/10. `V_CURSOR2` (= `crsarticole1`)
|
||||
e interogarea proprie contractului, cu `id_c` deliberat decalat cu `-10000` fata de spatiul lui
|
||||
`ROWNUM` simplu — dovada directa ca autorii codului au tratat coliziunea de `id_c` intre cele doua
|
||||
cursoare ca pe un risc real de evitat, nu ca pe ceva neglijabil.
|
||||
|
||||
**Consecinta pentru UI**: pe un document de contract, `grd_articole` (RecordSource `crsarticole`)
|
||||
afiseaza azi **lista de preturi**, nu articolele contractului — de aceea capul lui de coloana e deja
|
||||
"Cantitate in stoc" si mesajul e deja "Acest articol nu este pe stoc!"
|
||||
(`ofacturare.vc2:15129-15143`, citat integral):
|
||||
|
||||
```
|
||||
Case Inlist(poDate.tip, 2, 6)
|
||||
&& facturare pe baza de contract
|
||||
This.lb_titlu_alb_b121.Caption = [FACTURA PE CTR. ] + Alltrim(poDate.descriere)
|
||||
This.grd_articole.RemoveObject('cSerie')
|
||||
&& articole din lista de preturi
|
||||
This.grd_articole.cCantitate.header1.Caption = [Cantitate in stoc]
|
||||
This.cmesaj_cantitate = [Acest articol nu este pe stoc!]
|
||||
```
|
||||
|
||||
`grd_contracte` (RecordSource `crsarticole1`) e gridul secundar, cu articolele/ratele contractului.
|
||||
Contractul "are deja lista de preturi" pentru ca **asa e populat de la inceput** — nu exista niciun
|
||||
`APPEND`/merge, doar doua interogari separate aratate in doua griduri diferite.
|
||||
|
||||
**Confirmarea ca registrul nu se aplica pe contract**: `do_scrie_factura`'s `Do Case`
|
||||
(`ofacturare.vc2:14282-14389`) are exact patru ramuri — `poDate.eProforma=1`, `poDate.Tip=4`,
|
||||
`Inlist(poDate.Tip,3,21,25,28,42,47)`, `Otherwise`. Tipurile de contract (2,6,26,52) nu apar in
|
||||
niciuna din primele trei, deci cad pe `Otherwise` — `pack_facturare.scrie_factura2` se cheama direct,
|
||||
**fara** `Select crsarticole / Calculate Sum(cantitate) ...` si fara `pnParametruAditional` derivat
|
||||
din vreo suma. Contractul nu are, azi, nicio decizie de "inchidere automata" bazata pe
|
||||
`Sum(crsarticole.cantitate)` — deci amestecul de continut din `crsarticole` (lista de preturi) nu are
|
||||
cum sa strice o logica ce nu exista pentru acest tip. **Pe comanda, aceeasi ramura a Do Case-ului
|
||||
(`Inlist(poDate.Tip,3,21,25,28,42,47)`) exista si citeste exact `crsarticole`**
|
||||
(`ofacturare.vc2:14332-14338`, citat in verdict) — de aici diferenta reala.
|
||||
|
||||
## 3. Proiectarea adaugarii
|
||||
|
||||
**Liniile libere NU intra in `crsarticole`.** Mecanismul recomandat:
|
||||
|
||||
1. **Sursa de date**: cursorul filtrat construit de S4 —
|
||||
`pack_facturare.cursor_preturi` supraincarcat cu `V_FILTRU_COD`/`V_FILTRU_DEN`
|
||||
(`docs\cercetare\s4_cautare_articole_server.md`, punctul 4/9, pasul 1). Nu se cere nimic nou
|
||||
in Oracle pentru S4e insusi — se reutilizeaza mecanismul deja proiectat pentru S4 (S4e devine
|
||||
inca un consumator al aceluiasi cursor filtrat, nu un cursor nou). "Alege din nomenclator…"
|
||||
(S4g, decizia 27/34) e un cursor separat, in afara acestei povesti.
|
||||
2. **Legarea in UI**: `APPEND BLANK` direct pe `crsfactura`, urmat de editare inline prin
|
||||
`combosql` pe celula de cod/denumire — exact tiparul deja existent, azi, in productie (chiar
|
||||
daca in celalalt formular) la `frm_facturare_articole2.But_nou1.do_adauga`
|
||||
(`ofacturare.vc2:17118-17122`):
|
||||
```
|
||||
PROCEDURE do_adauga
|
||||
Select crsFactura
|
||||
APPEND BLANK
|
||||
this.grd_factura.SetFocus()
|
||||
ENDPROC
|
||||
```
|
||||
Meniul S4b ("Cauta in lista de preturi…") declanseaza acest gest, cu focus direct pe celula de
|
||||
cautare — S4b insusi a documentat aceasta echivalenta (sectiunea 6 a raportului S4b): "aceste
|
||||
doua optiuni de meniu ajung la acelasi gest UI".
|
||||
3. **`crsfactura.id_c` ramane la valoarea implicita a campului** (`N(20)`, fara `Null`,
|
||||
`creeaza_facturacrs`, `COMUN\programe\ofacturare_comun.prg:1775-1785`) — adica `0` la
|
||||
`APPEND BLANK`, valoare pe care Oracle nu o produce niciodata pentru `id_c` (`ROWNUM` incepe de
|
||||
la 1). Consecinta: `do_sterge`-ul de azi (`For id_c = poArticol.id_c`, vezi sectiunea 4) nu
|
||||
gaseste niciodata un rand de potrivit in `crsarticole` pentru o linie libera — comportament
|
||||
corect prin constructie, **fara nicio modificare de cod**.
|
||||
*Precedent in acelasi fisier, deja in productie*: linia de discount adaugata la finalul
|
||||
documentului (`ofacturare.vc2:14531-14537`, `Select crsfactura / Append Blank / Replace
|
||||
denumire With Replicate('Z',20), ... , id_temp With 99999`) — nu seteaza `id_c` deloc, foloseste
|
||||
deja acelasi tipar de "rand sintetic fara sursa in `crsarticole`".
|
||||
4. **`crsfactura.opt_facturare`** (numeric, implicit `0`) ramane la valoarea implicita si pentru o
|
||||
linie libera — coincide cu valoarea pe care o are deja o linie normala din lista de preturi
|
||||
(`opt_facturare=0` inseamna azi "nu vine din contract", conventia citita de `do_sterge` la
|
||||
`ofacturare.vc2:14615-14619`). Nu e nevoie de o valoare noua pe acest camp pentru distinctia
|
||||
"libera vs. comanda" — distinctia se face deja prin `id_c` (punctul 3).
|
||||
5. **Completarea campurilor de pret/TVA/valuta**: identic cu maparea deja proiectata de S4
|
||||
(`s4_cautare_articole_server.md`, punctul 6 — tabelul camp-cu-camp) — S4e nu adauga camp nou,
|
||||
preia mecanismul S4 ca atare pentru randul ales din cautarea filtrata.
|
||||
|
||||
**Ce ramane de verificat la implementare, nu de presupus**: daca `combosql`-ul legat pe `grd_factura`
|
||||
(construit pentru S4) trebuie sa apara pe **toate** coloanele relevante (cod, denumire) ale unei
|
||||
linii nou-adaugate prin acest gest, sau doar pe coloana pe care s-a facut click — comportamentul
|
||||
exact al `LostFocus`-ului de completare in masa a celorlalte campuri (`s4_cautare_articole_server.md`,
|
||||
punctul 5, pasul 4) ramane in perimetrul S4, S4e doar il consuma.
|
||||
|
||||
## 4. Stergerea
|
||||
|
||||
**(a) O linie venita din comanda** — comportament **neschimbat**, decizia 29 se aplica ca azi:
|
||||
`do_sterge` (`ofacturare.vc2:14608-14693`) gaseste `poArticol.opt_facturare = 0` (linie din
|
||||
`crsarticole`, comanda), potriveste pe `id_c` (`ofacturare.vc2:14652-14655`,
|
||||
`Case (gnScadereStoc = 1 And poDate.tip = 23) Or Inlist(poDate.tip,3,4,21,25,28,42,47) => Select
|
||||
crsarticole / Replace cantitate With cantitate + poArticol.cantitate For id_c = poArticol.id_c`),
|
||||
reface cantitatea ramasa in `crsarticole`. La `do_scrie_factura`, `Calculate Sum(cantitate)` peste
|
||||
`crsarticole` (neschimbat, fara linii libere in el) vede corect cantitatea marita — comanda ramane
|
||||
"facturata partial", fara nicio interventie noua. Nicio confirmare, niciun marcaj "refuzat" —
|
||||
exact decizia 29.
|
||||
|
||||
**(b) O linie libera adaugata** — `poArticol.opt_facturare = 0` (aceeasi valoare ca (a): `0` nu
|
||||
distinge intre "din lista de preturi ca supliment pe comanda" si "din lista de preturi ca document
|
||||
liber" — nu trebuie sa distinga, pentru ca in ambele cazuri `lcCursor = crsarticole`
|
||||
la `ofacturare.vc2:14615-14619`). Executia lui `Replace cantitate With cantitate + poArticol.cantitate
|
||||
For id_c = poArticol.id_c` gaseste **zero randuri** in `crsarticole` cu `id_c = 0` (Oracle nu produce
|
||||
niciodata `id_c = 0`), deci `REPLACE ... FOR` e un no-op — nimic nu se schimba in registru. Corect: o
|
||||
linie libera nu a fost niciodata scazuta din vreun "ramas de facturat", deci nu are ce sa refaca la
|
||||
stergere. **Nicio schimbare de cod necesara in `do_sterge` pentru acest caz.**
|
||||
|
||||
**Verificare de facut la implementare, nu presupunere**: confirmarea exacta a valorii implicite
|
||||
(`0` vs. posibil `.NULL.` daca proprietatile campului se schimba fata de ce arata azi
|
||||
`creeaza_facturacrs`) — ambele valori produc acelasi rezultat (`REPLACE ... FOR` fara potrivire),
|
||||
dar merita un test explicit inainte de a considera punctul inchis, nu doar citirea definitiei
|
||||
campului.
|
||||
|
||||
## 5. Capul de coloana si mesajul de cantitate
|
||||
|
||||
**Corectie fata de ipoteza initiala a task-ului**: capul de coloana "Cantitate comandata" si mesajul
|
||||
"A fost facturata intreaga cantitate comandata pentru acest articol!" (`ofacturare.vc2:15144-15150`)
|
||||
apartin lui `grd_articole`/`crsarticole` — gridul care, cu proiectarea din sectiunea 3, **continua sa
|
||||
arate exclusiv liniile comenzii** (liniile libere nu intra niciodata acolo). Deci acest cap de coloana
|
||||
si acest mesaj **raman corecte, neschimbate**, pentru ca nu se mai aplica vreodata unei linii libere.
|
||||
|
||||
**Unde apare totusi problema semnalata de task**: nu in `grd_articole`, ci in **validarea de
|
||||
cantitate/stoc a liniei libere insesi**, care trebuie sa arate un mesaj propriu, nu mostenit de la
|
||||
`Thisform.cmesaj_cantitate` (proprietate unica per formular, setata o singura data in `Init` dupa
|
||||
`poDate.tip` — daca `poDate.tip = 3`, ea contine azi tocmai textul de comanda). Daca validarea unei
|
||||
linii libere ar reutiliza `Thisform.cmesaj_cantitate` fara sa o schimbe pe moment, un articol liber
|
||||
epuizat din stoc ar afisa gresit "A fost facturata intreaga cantitate comandata" — mesajul cerut
|
||||
de plan la punctul de atentie e real, doar localizat gresit initial (nu pe `grd_articole`, ci pe
|
||||
mecanismul de validare a liniei libere, vezi sectiunea 6).
|
||||
|
||||
**Propunere concreta de text** (romana, de validat cu Marius la implementare):
|
||||
- `grd_articole` (comanda), neschimbat: cap coloana **"Cantitate comandata"**, mesaj
|
||||
**"A fost facturata intreaga cantitate comandata pentru acest articol!"**.
|
||||
- Linie libera (validare separata, sectiunea 6), text propus, calchiat dupa formularea deja
|
||||
folosita pe lista de preturi simpla (`ofacturare.vc2:15122-15127`, tipurile 1/5/7/10, unde
|
||||
`cmesaj_cantitate` ramane la valoarea implicita a formularului): **"Nu mai exista stoc disponibil
|
||||
pentru acest articol!"** — coerent cu formularea deja folosita pe contract
|
||||
(`[Acest articol nu este pe stoc!]`, `ofacturare.vc2:15136`), fara sa foloseasca acelasi text
|
||||
identic (ca sa nu para o dublare accidentala a mesajului de contract).
|
||||
|
||||
## 6. Validarea de cantitate
|
||||
|
||||
**Ce se pastreaza neschimbat**: pentru liniile din comanda, alegerea prin `grd_articole` ramane pe
|
||||
calea de azi — `do_adauga_articol` -> `do_verifica_articol(tlContract=.F., tnCantitate=poArticol.cantitate)`
|
||||
(`ofacturare.vc2:12813-12869`, `:14731-14787`). `poArticol.cantitate` provine din
|
||||
`crsarticole.cantitate`, care e "ramas de facturat" (`A.CANTITATE - NVL(D.CANTITATE,0)`,
|
||||
`PACK_FACTURARE:3030`) — cererea din plan ("liniile din comanda sa nu-si piarda validarea de
|
||||
cantitate") e satisfacuta **prin constructie**: acest drum nu e atins de nimic din proiectarea S4e,
|
||||
pentru ca liniile libere nu intra niciodata in `crsarticole` si nu modifica randurile lui.
|
||||
|
||||
**Ce nu se poate reutiliza ca atare pentru o linie libera**: `do_verifica_articol` cere un
|
||||
`poArticol` scatter-uit dintr-un cursor sursa deja incarcat (`crsarticole`/`crsarticole1`) — o linie
|
||||
libera adaugata prin `APPEND BLANK` + `combosql` (sectiunea 3) nu are un asemenea rand sursa. Capacul
|
||||
de cantitate pentru un articol gestionabil ales liber trebuie sa vina din **stocul curent al
|
||||
articolului**, nu din "ramas de facturat" — informatie pe care randul intors de cautarea filtrata
|
||||
(`cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`) o are deja in coloana `cantitate` (aceeasi
|
||||
semantica de stoc descrisa in verdict), fara sa fie nevoie de un apel Oracle suplimentar.
|
||||
|
||||
**Gol real de proiectare, necesar S4e, neacoperit de niciun raport anterior (S4/S4b)**: nici
|
||||
`s4_cautare_articole_server.md`, nici `s4b_bara_butoane_meniu.md` nu descriu un mecanism de validare
|
||||
a cantitatii/stocului pentru randul ales prin `combosql` pe `grd_factura` — ambele rapoarte se opresc
|
||||
la "randul se completeaza cu valorile din cursorul filtrat" (mapare de campuri), fara sa acopere
|
||||
"ce se intampla daca utilizatorul introduce o cantitate mai mare decat stocul". **De proiectat la
|
||||
implementare**: fie (a) o verificare in `LostFocus`/`Valid` a celulei de cantitate din `grd_factura`,
|
||||
similara ca forma cu `do_verifica_articol` dar alimentata din randul `combosql` (nu din
|
||||
`crsarticole`), fie (b) reutilizarea `do_verifica_articol` insasi, cu un `poArticol` construit ad-hoc
|
||||
din randul `combosql` in loc de `Scatter` dintr-un cursor — a doua varianta pastreaza un singur loc
|
||||
de validare in cod, dar cere ca `do_verifica_articol` sa primeasca `poArticol` ca parametru explicit
|
||||
in loc sa citeasca variabila `Private poArticol` deja populata de apelant (schimbare de contract a
|
||||
metodei, nu doar de continut). Ambele variante trebuie sa aleaga mesajul din sectiunea 5, nu
|
||||
`Thisform.cmesaj_cantitate` motenit din `Init`.
|
||||
|
||||
## 7. Aceeasi problema pe alte surse
|
||||
|
||||
| Sursa | `crsarticole` = registru citit de `do_scrie_factura`? | `do_sterge` ajusteaza `crsarticole` pe `id_c`? | Concluzie pentru "APPEND FROM literal" |
|
||||
|---|---|---|---|
|
||||
| **comanda** (3,21,25,28,42,47) | **Da** — `Inlist(poDate.Tip,3,21,25,28,42,47)`, `:14332-14338` | **Da** — aceleasi tipuri, `:14652-14655` | **Nesigur** — subiectul acestui raport; solutia e "linii libere in afara `crsarticole`" (sectiunea 3) |
|
||||
| **avize** (4, "factura din avize") | **Da** — `poDate.Tip = 4`, `:14301-14311` | **Da** — `Inlist(poDate.tip,3,4,21,25,28,42,47)` include 4, `:14652-14655` | **Acelasi risc ca pe comanda** — `cursor_avize` are aceeasi semantica "ramas de facturat" (`docs\cercetare\s4_cautare_articole_server.md`, punctul 1.4); solutia din sectiunea 3 se aplica identic |
|
||||
| **contract** (2,6,26,52) | **Nu** — tipurile nu apar in `Do Case`-ul din `do_scrie_factura`, cad pe `Otherwise` | Da, dar pe `crsarticole1` (`opt_facturare<>0`), niciodata pe `crsarticole` | **Sigur** — lista de preturi traieste deja intr-un cursor separat (`crsarticole`), fara sa fie citita ca registru; nimic de schimbat |
|
||||
| **retur ca document** (8,9,24) | **Nu** — cad pe `Otherwise`, fara `Sum(cantitate)`/`pnParametruAditional` | **Da, partial** — `Inlist(poDate.tip,8,9,24) => Replace cantitate With cantitate - poArticol.cantitate For id_c = poArticol.id_c`, `:14658-14659` (semn opus, urmareste maximul returnabil, nu inchiderea) | **Risc de coliziune `id_c` ramane, dar fara poluarea `Sum`**: o linie libera adaugata literal prin `APPEND FROM` in `crsarticole` pe un document de retur ar putea, la stergere, atinge tacut cantitatea maxima returnabila a unei linii reale (aceeasi problema de `id_c` din verdict, punctul 1) — **de tratat cu aceeasi solutie (linii libere in afara `crsarticole`) si in S4f**, nu doar aici. Decizia 22 (retur fara factura originala e permis) nu schimba aceasta concluzie — priveste alt aspect (mostenirea gestiunii/pretului) |
|
||||
| **transfer subunitati** (23, 41) | Nu (cad pe `Otherwise`) | Partial, doar tip 41 (`Case gnScadereStoc = 1 And poDate.tip = 41`, `:14647-14649`, cu semn opus) | In afara perimetrului S4e (surse via `cursor_gestiune`, nu `cursor_comanda`/`cursor_preturi`); acelasi principiu de precautie se aplica daca se cere vreodata "lista de preturi si pe transfer" |
|
||||
|
||||
**Concluzie de tabel**: comanda si avize sunt genuin identice ca risc si ca solutie — **orice
|
||||
implementare a S4e trebuie sa acopere ambele tipuri de sursa cu acelasi mecanism** ("linii libere in
|
||||
afara `crsarticole`"), nu doar comanda cum sugereaza titlul povestii. Contractul ramane cum e azi
|
||||
(nimic de schimbat). Returul ca document (S4f) trebuie avertizat explicit sa nu foloseasca reteta
|
||||
`APPEND FROM` literal, desi riscul lui e mai mic (fara poluarea sumei de inchidere).
|
||||
|
||||
## 8. Pasi de implementare
|
||||
|
||||
1. **Extinde meniul S4b cu "Cauta in lista de preturi…" pe comanda/aviz** (deja in tabelul propus de
|
||||
S4b, sectiunea 3 a acelui raport) — leaga optiunea de gestul `APPEND BLANK` pe `crsfactura`
|
||||
descris in sectiunea 3 de mai sus, nu de un al doilea cursor bulk.
|
||||
*Verificabil*: pe un document de tip 3 (comanda) sau 4/21/25/28/42/47 (avize), alegerea optiunii
|
||||
adauga un rand gol in `grd_factura` cu focus pe celula de cautare, fara niciun apel catre
|
||||
`cursor_comanda`/`cursor_avize` in plus (verificabil in logul `goExecutor`).
|
||||
2. **Confirma valoarea implicita a `crsfactura.id_c` la `APPEND BLANK`** (sectiunea 4) — test direct,
|
||||
nu citire de definitie. *Verificabil*: dupa `APPEND BLANK` pe un `crsfactura` gol, `id_c` al
|
||||
noului rand e o valoare pe care Oracle n-o produce niciodata pentru `id_c` (azi: `0`).
|
||||
3. **Adauga validarea de cantitate/stoc pentru linia libera** (sectiunea 6, varianta aleasa la
|
||||
implementare) — mesaj propriu, nu `Thisform.cmesaj_cantitate` mostenit.
|
||||
*Verificabil*: pe un articol gestionabil cu stoc 0, adaugarea lui ca linie libera pe un document
|
||||
de comanda arata mesajul de stoc propus in sectiunea 5, nu mesajul de "cantitate comandata".
|
||||
4. **Verifica prin regresie ca `do_sterge`/`do_scrie_factura` raman neatinse** — niciun cod nou in
|
||||
aceste doua metode pentru cazul liniei libere (confirmat teoretic in sectiunea 4, dar de validat
|
||||
pe date reale). *Verificabil*: pe un document de comanda cu o linie a comenzii si o linie libera
|
||||
adaugate, stergerea liniei libere nu modifica `Calculate Sum(cantitate)` peste `crsarticole`;
|
||||
stergerea liniei din comanda reface cantitatea ei, neschimbat fata de azi.
|
||||
5. **Extinde acelasi mecanism pe avize (tip 4/21/25/28/42/47)** — nu doar comanda (tabelul din
|
||||
sectiunea 7). *Verificabil*: identic cu pasul 4, pe un document "factura din avize".
|
||||
6. **Avertizeaza explicit S4f (retur ca document)** sa nu foloseasca `APPEND FROM` literal pentru
|
||||
lista de preturi pe documentele de retur (8,9,24) — risc de coliziune `id_c` cu maximul returnabil,
|
||||
chiar daca fara poluarea sumei de inchidere. Nu e un pas de implementat aici, e o nota de predare
|
||||
catre acea poveste (deja referentiata la decizia 22/S4f in plan).
|
||||
|
||||
## 9. Ce nu se poate testa headless
|
||||
|
||||
- **Comportamentul `combosql`/`APPEND BLANK` in `grd_factura`** — capcana deja confirmata pe acest
|
||||
proiect (`grid-coloane-nu-se-materializeaza-headless`): sub `-A -T`, `ColumnCount`/`RecordSource`
|
||||
raman artefacte. Verificarea vizuala a mesajului/capul de coloana (sectiunea 5) si a gestului de
|
||||
adaugare (sectiunea 3) cere harnessul UI vizibil, nu rulare headless.
|
||||
- **`LostFocus`/`Valid` pe celula de cantitate** (sectiunea 6) — daca implementarea alege varianta
|
||||
(a) din sectiunea 6 (verificare directa in eveniment de celula), comportamentul depinde de randare
|
||||
de grid, deci nu e testabil direct headless; varianta (b) (reutilizare `do_verifica_articol` cu
|
||||
`poArticol` construit ad-hoc) **este** apelabila programatic, fara UI, daca semnatura metodei
|
||||
primeste parametrul explicit.
|
||||
- **`AMESSAGEBOX` la mesajul de stoc** (sectiunea 5) — verificabil ca text literal in cod, nu ca
|
||||
aparitie reala pe ecran, din acelasi motiv structural ca oriunde altundeva in suita.
|
||||
- **Ce se poate verifica headless, direct**: continutul `crsfactura`/`crsarticole` dupa apeluri
|
||||
directe (nu prin click) la `do_sterge`/`do_scrie_factura`/gestul de `APPEND BLANK`, cu date de
|
||||
test pregatite manual (un `crsarticole` cu 2-3 randuri de comanda, un `crsfactura` cu o linie de
|
||||
comanda si una libera inserate programatic) — confirma `id_c`, `Sum(cantitate)`, si comportamentul
|
||||
`REPLACE ... FOR` fara sa deschida formularul.
|
||||
|
||||
## 10. Riscuri si decizii ramase lui Marius
|
||||
|
||||
1. **Metoda de validare a cantitatii pe linia libera** (sectiunea 6) — varianta (a) sau (b); (b)
|
||||
cere schimbarea contractului lui `do_verifica_articol` (parametru nou `poArticol`), (a) dubleaza
|
||||
logica de validare in doua locuri diferite ale codului. **Recomandare**: (b), pentru un singur
|
||||
loc de intretinut — coerent cu principiul deja aplicat in S4b sectiunea 5 ("echivalenta tot vs.
|
||||
alege" tine pe convergenta catre o singura rutina).
|
||||
2. **Textul exact al mesajului de stoc pentru linia libera** (sectiunea 5) — propunerea e o
|
||||
recomandare, nu o decizie finala; de confirmat impreuna cu textele deja stabilite la S3b
|
||||
(punctul marunt `a` din plan, `:524`).
|
||||
3. **S4f trebuie avertizat explicit sa nu repete reteta `APPEND FROM`** pentru retur (sectiunea 7,
|
||||
pasul 6) — nu e o decizie de luat aici, dar cineva trebuie sa o duca mai departe cand S4f intra
|
||||
in lucru, altfel riscul de coliziune `id_c` pe retur se reintroduce pe alta poveste, fara sa mai
|
||||
fie legat de acest raport.
|
||||
4. **Interactiunea cu S4 punctul 2, in lucru la momentul acestui raport** — proiectarea de aici
|
||||
presupune ca liniile libere raman complet in afara oricarui mecanism de registru, indiferent cum
|
||||
arata forma lui finala dupa decuplare. Daca punctul 2 decide sa scoata bookkeeping-ul din
|
||||
`crsarticole` intr-un cursor nou (cea mai probabila directie, judecand dupa decizia 39), concluzia
|
||||
sectiunilor 3-4 de aici ramane valabila fara nicio schimbare — liniile libere nu ating niciun
|
||||
cursor de registru, indiferent de numele lui. Daca punctul 2 alege in schimb sa pastreze
|
||||
`crsarticole` ca sursa unica dar sa filtreze `Sum(cantitate)`/`do_sterge` printr-un camp nou
|
||||
(de exemplu un flag `liber`), acel camp ar trebui setat pe liniile libere **daca** ele ar intra
|
||||
totusi in `crsarticole` — proiectare pe care acest raport o considera inutila (liniile libere nu
|
||||
trebuie sa intre acolo deloc), dar merita re-confirmat cu punctul 2 cand acel raport se incheie,
|
||||
ca sa nu ramana doua solutii partial suprapuse pentru aceeasi problema.
|
||||
5. **Nu s-a masurat, doar dedus din forma interogarii** (decizia 31 a planului, aplicata si aici):
|
||||
nimic din acest raport se sprijina pe volume de date masurate — toate concluziile despre `id_c`/
|
||||
`Sum(cantitate)` vin din citirea directa a SQL-ului si a codului VFP, nu din executii de test.
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare incheiata, fara nicio problema de context intalnita in aceasta runda — nu a fost necesara
|
||||
predarea. Toate cele zece sectiuni cerute de brief sunt completate mai sus, cu `fisier:linie`
|
||||
verificat direct pe fisierele text reale (nu `.bak`) si pe SQL-ul exportat curent
|
||||
(`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`). Nicio editare
|
||||
de cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun commit, nicio scriere pe Oracle.
|
||||
|
||||
**Corectie fata de premisa initiala a task-ului**, de retinut explicit pentru cine citeste doar
|
||||
rezumatul: reteta citata in decizia 16 (`APPEND FROM`, `ofacturare.prg:454-473`) **nu** trebuie
|
||||
generalizata literal pe comanda — e sigura doar in contextul ei actual (copiere, fara registru activ
|
||||
pe cursorul respectiv). Solutia corecta, verificata ca fezabila si ca deja partial sprijinita de
|
||||
mecanisme existente (S4's `cursor_preturi` filtrat, `APPEND BLANK`-ul din `frm_facturare_articole2`,
|
||||
valoarea implicita `0` a lui `id_c`), e ca liniile libere sa **nu intre niciodata in `crsarticole`**
|
||||
— exact ipoteza pe care brief-ul o semnala ca posibila si cerea sa fie confirmata sau infirmata pe
|
||||
cod, nu presupusa.
|
||||
727
docs/cercetare/s4f_retur_formular_unificat.md
Normal file
727
docs/cercetare/s4f_retur_formular_unificat.md
Normal file
@@ -0,0 +1,727 @@
|
||||
# S4f — Returul in formularul unificat
|
||||
|
||||
Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara
|
||||
scriere Oracle — doar `SELECT`), pentru povestea **S4f** din
|
||||
`docs\plan_13_unificare_formular_facturare.md:2287-2330` (decizia 17, proiectata in sectiunea N,
|
||||
`:1574-1655`). Nu s-a atins `COMUN\clase\ofacturare_comun.vc2` si
|
||||
`COMUN\programe\ofacturare_editare.prg` — doar citite, cand au aparut in cautari.
|
||||
|
||||
Surse de adevar deja stabilite, citate nu reinvestigate: `docs\cercetare\legatura_linie_retur.md`,
|
||||
`docs\cercetare\factura_retur_document.md`, `docs\cercetare\retur_si_lista_preturi.md`,
|
||||
`docs\cercetare\s4b_bara_butoane_meniu.md`.
|
||||
|
||||
## Verdict (esenta, 10 randuri)
|
||||
|
||||
**Corectat dupa avertismentul S4e** (`docs\cercetare\s4e_lista_preturi_pe_sursa.md`, sectiunile 3 si
|
||||
7): mecanismul `APPEND FROM` din decizia 16 (`ofacturare.prg:454-473`) **e respins pentru orice
|
||||
cursor care serveste si drept registru citit de `do_sterge`**, `crsarticole` fiind exact acel cursor
|
||||
pe documentele de retur (`Inlist(poDate.tip,8,9,24) => Replace cantitate With cantitate -
|
||||
poArticol.cantitate For id_c = poArticol.id_c`, `ofacturare.vc2:14658-14659`) — riscul e coliziune de
|
||||
`id_c` intre `cursor_retur_document` si `cursor_preturi`, ambele numerotate `ROWNUM` independent.
|
||||
Sectiunea 5 de mai jos e rescrisa pe solutia validata de S4e: **liniile libere nu intra deloc in
|
||||
`crsarticole`**, se adauga direct in `crsfactura` prin `APPEND BLANK` + `combosql` (tiparul deja in
|
||||
productie la `frm_facturare_articole2.But_nou1.do_adauga`), iar campul de distinctie e
|
||||
**`crsfactura.id_c`** (`0` implicit pe o linie libera, valoare pe care Oracle n-o produce niciodata
|
||||
pentru `id_c`), nu o coloana lipsa in `crsarticole`.
|
||||
|
||||
Partea grea nu e ce parea: N.1 (documentul de retur, tip 8/9/24) merge deja cap-coada — alegere
|
||||
multipla de facturi, populare din `cursor_retur`, gestiune si pret de achizitie mostenite, stergere
|
||||
si retur partial. Munca reala e in trei bucati inguste. **(1) Nu se muta dialogul de alegere a
|
||||
facturilor** in bara de butoane a gridului — s-a verificat pe cod ca alegerea ramane la nivelul
|
||||
antetului (sectiunea pliata), exact cum recomanda deja `s4b_bara_butoane_meniu.md` §9.3/tabelul
|
||||
"puncte marunte", randul h; ce se muta in meniul "Adauga articole" e **rezultatul** alegerii (liniile
|
||||
deja aduse), ca "Adauga tot din facturile alese" / "Alege liniile de returnat…", simetric cu
|
||||
comanda/contract/avize. **(2)** Ridicarea lui `But_retur` la nivel de document e cod nou real, nu o
|
||||
mutare de buton — azi alegerea e per-articol si niciodata scrisa ca legatura persistata.
|
||||
**(3)** Liniile libere pe retur folosesc reteta S4e ("`APPEND BLANK` pe `crsfactura`, niciodata pe
|
||||
`crsarticole`"), **plus** un gol nou de proiectat, gasit in aceasta sesiune si necunoscut de S4e (care
|
||||
nu are dialog de gestiune pe comanda/avize): mecanismul de alegere a gestiunii
|
||||
(`frm_articol_gest_factura`, `.lRetur=.T.`) **suprascrie pretul de vanzare cu pretul din stoc**
|
||||
(`ofacturare.vc2:4094-4103`) — corect pentru o linie mostenita dintr-o factura sursa, dar gresit
|
||||
pentru o linie libera, a carei pret de vanzare trebuie sa vina din lista de preturi (decizia 16), nu
|
||||
din costul de stoc; in plus, acel dialog se intra azi doar dintr-un `poArticol` scatter-uit din
|
||||
`crsarticole` (`do_adauga_articol`/`do_alege_stoc`), pe care o linie libera din `crsfactura` nu-l are
|
||||
— e nevoie de un punct de intrare nou in dialog, nu doar de reparat pretul. Vezi sectiunea 5 si
|
||||
riscurile R1/R7.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventarul celor doua cai de retur de azi
|
||||
|
||||
### 1.1 N.1 — factura de retur ca document (tip 8, 9, aviz 24)
|
||||
|
||||
| Pas | Ce face | `fisier:linie` |
|
||||
|---|---|---|
|
||||
| Intrare | Tile `Page2.Cw1` -> `politica.mpr` -> `factureaza(8)`/`factureaza(9)` din meniul shortcut | `COMUN\clase\ofundal_facturare.vc2:882-884`, `Meniuri\politica.mn2:14-15,45-46` |
|
||||
| Antet | `nIdTipDoc=5`, formular `frm_date_factura`, aviz retur (24) foloseste `frm_date_aviz` cu `nIdTipDoc=6` | `COMUN\programe\ofacturare.prg:192-196,222-226` |
|
||||
| Alegere facturi sursa | `frm_date_factura.do_cauta_facturi` -> `caut_facturi_multiple_client(id_client, in_valuta, id_valuta, .T.)` -> `cauta_alfa(..., tnTipReturn=1)`, selectie multipla, exclude tipurile de retur din sursa | `COMUN\clase\ofacturare.vc2:9173-9212`; `COMUN\programe\oproceduri_facturare.prg:2091-2121` |
|
||||
| Validare obligatorie | Fara alegere, antetul nu se poate inchide (`inainte_de_do_termin`) | `ofacturare.vc2:9523-9526` |
|
||||
| Populare linii | `pack_facturare.cursor_retur(V_IN_VALUTA,V_LISTAID,V_ID_UTIL)` -> `cursor_retur_document(...,V_COPIERE=0,...)`, `SELECT` direct din `VANZARI_DETALII`, filtrat pe `ID_VANZARE IN (V_LISTAID)` | `ofacturare.prg:306-307`; `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:3943-3956,3958-4071` |
|
||||
| Gestiune + pret achizitie | `ID_GESTIUNE`/`PRET_ACHIZITIE` copiate neschimbate din linia originala; `GESTIONABIL = NVL2(A1.ID_GESTIUNE,1,0)` | `ff_...sql:4034-4035,4055-4056,4048` |
|
||||
| Stergere linie | Permisa, cantitatea reintra in cursorul sursa | `ofacturare.vc2:14608-14693`, ramura `:14658-14659` |
|
||||
| Retur partial | Permis, validat fata de maximul = `CANTITATE` a liniei originale, fara agregare cu retururi anterioare | `ofacturare.vc2:14743-14754`, `ff_...sql:4036` |
|
||||
| Adaugare libera | **Nu se poate azi** — `do_adauga_articol` ia articolul mereu din `crsarticole`, care pe tip 8/9/24 **este** rezultatul lui `cursor_retur` | `ofacturare.vc2:12843-12851` |
|
||||
|
||||
### 1.2 N.2 — `But_retur` per-articol, in factura normala (tip 1, 5, 7, 10)
|
||||
|
||||
| Pas | Ce face | `fisier:linie` |
|
||||
|---|---|---|
|
||||
| Vizibilitate | Doar pe tip 1/5/7/10; pe documentele de retur (8/9/24) butonul nu exista in UI | `ofacturare.vc2:15122-15127` |
|
||||
| Declansare | `caction=do_retur` -> `do_adauga_articol(.F.,.F.,.T.)` | `cmd_butoane.vc2:324-338`; `ofacturare.vc2:13963-13965` |
|
||||
| Alegere factura sursa | **Per articol**, la fiecare adaugare: `caut_facturi_multiple_client_articol(id_client,in_valuta,id_valuta,.F.,id_articol)` -> `cauta_alfa` cu titlu simplu "Alegeti factura" (nu selectie multipla) | `oproceduri_facturare.prg:2124-2159` |
|
||||
| Acumulare | `thisform.cListaIdArticoleRetur` acumuleaza CSV de perechi `id_articol:id_vanzare`, cate una per articol adaugat; `poDate.listaid` e folosit **transient** (salvat in `lnListaIdOld` si restaurat imediat dupa) doar cat dureaza dialogul per-articol | `ofacturare.vc2:12882-12892` |
|
||||
| Gestiune | **Se alege**, nu se mosteneste — `pack_facturare.cursor_gestiuni_articol_retur`, prin `do_alege_stoc(...,tlRetur=.T.)` -> `frm_articol_gest_factura` | `ofacturare.vc2:13230,13353-13385` |
|
||||
| Pret | **Suprascris cu pretul din stoc** (`pretv`), nu cel din lista de preturi — vezi sectiunea 5 | `ofacturare.vc2:4094-4103` |
|
||||
| Semn cantitate | Explicit `poArticol.cantitate = (-1) * cantitate` cand `Thisform.lRetur` | `ofacturare.vc2:4103` |
|
||||
| Trimitere Oracle | `poDate.listaid = thisform.cListaIdArticoleRetur` la scriere finala | `ofacturare.vc2:13977-13979` |
|
||||
| Legatura persistata | **Nicio legatura scrisa** — `listaid` e folosit doar ca filtru in calculul de disponibil pe rulaj (`RUL`), nu scrie nimic in `VANZARI_CORESP` sau pe linia noua | `ff_...sql:8142-8212` |
|
||||
|
||||
### 1.3 Ce au in comun si unde diverg
|
||||
|
||||
Comun: excluderea returului dintr-un retur (acelasi filtru `tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11)`
|
||||
in ambele functii de cautare), conventia de semn negativ pe cantitate, `frm_articol_gest_factura`
|
||||
ca formular de alegere a gestiunii pentru linia gestionabila.
|
||||
|
||||
Diverg pe tot restul: N.1 alege facturile **o singura data, la nivel de document, inainte** ca
|
||||
articolele sa existe; N.2 alege **per articol, in orice moment**, fara limita de cate ori. N.1
|
||||
mosteneste gestiune+pret_achizitie fara alegere; N.2 le cere explicit. N.1 scrie
|
||||
`VANZARI_CORESP(TIP=3)`; N.2 nu scrie nimic persistat. Zero cod comun intre cele doua cai —
|
||||
`ofacturare.vc2:12813-12900` (N.2, `do_adauga_articol`) si `ofacturare.prg:306-311` (N.1,
|
||||
`cursor_retur`) nu se ating.
|
||||
|
||||
---
|
||||
|
||||
## 2. `do_cauta_facturi` / `cauta_alfa(..., tnTipReturn=1)` — contractul exact
|
||||
|
||||
### 2.1 Cine il cheama si cand
|
||||
|
||||
Pe `frm_date_factura` exista un control generic de cautare, `Ct_clb_altele` (clasa
|
||||
`ct_clb_cautare`), a carui iconita de lupa e legata de `cprocedura = thisform.do_cauta_altele`
|
||||
(`ofacturare.vc2:8721-8729`). `do_cauta_altele` e un dispecer pe `nid_tip`:
|
||||
|
||||
```
|
||||
Case Inlist(Thisform.nid_tip,8,9)
|
||||
Thisform.do_cauta_facturi()
|
||||
```
|
||||
(`ofacturare.vc2:8953-8954`)
|
||||
|
||||
**Secventa confirmata pe cod** (`factureaza()`, `ofacturare.prg:187-311`): `frm_date_factura` se
|
||||
creeaza si se afiseaza (`.Show()`, `:230-235`) **inainte** ca `Do Case tnTip` de la `:266-308` sa
|
||||
ruleze `cursor_retur`, si mult inainte ca `lcObject = [frm_facturare_articole]` sa fie ales
|
||||
(`:395`). Executia continua abia dupa ce `ofrmceredate` (antetul) se elibereaza (`:248`), iar
|
||||
`pnButon` (setat la inchiderea antetului) e verificat imediat dupa. Deci **azi dialogul de alegere
|
||||
a facturilor ruleaza pe formularul de antet, complet inainte ca formularul de articole sa existe** —
|
||||
`crsarticole` se incarca o singura data, cu `poDate.listaid` deja fixat.
|
||||
|
||||
Control observat, semnificativ pentru sectiunea 3: `Ct_clb_altele` are
|
||||
`cconditie = EMPTY(NVL(poDate.listaid,''))` (`ofacturare.vc2:8722`) — sugereaza ca iconita de
|
||||
cautare e activa doar cat timp nimic nu a fost inca ales; **neverificat** insa exact ce proprietate
|
||||
citeste `cconditie` (clasa `ct_clb_cautare` nu a fost gasita in text in acest repo, posibil intr-un
|
||||
`.vcx` neconvertit sau in `COMUNROA`), deci nu se poate confirma daca astazi re-alegerea e blocata
|
||||
explicit sau doar descurajata vizual. De tratat ca ipoteza, nu ca fapt confirmat.
|
||||
|
||||
### 2.2 Contractul functiei
|
||||
|
||||
`caut_facturi_multiple_client(tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple)`
|
||||
(`oproceduri_facturare.prg:2091-2121`):
|
||||
- **Intrare**: client (obligatoriu — dialogul nu porneste fara el), valuta (doar daca `tnInValuta`),
|
||||
`tlFacturiMultiple=.T.` la acest apel -> `lnTipReturn=1` in `cauta_alfa`.
|
||||
- **Sursa**: `select serie_act,numar_act,data_act,dataora,id_vanzare from fact_vfacturi where
|
||||
sters=0 and tip in (1,2,3,4,5,6,10,-1,-2,-3,-4,-11) [+ conditie sucursala] [+ client] [+ valuta]`
|
||||
— fara filtru SQL pe perioada/serie (filtrarea pe acele coloane e comportament generic al
|
||||
`cauta_alfa`, nu parametru dedicat).
|
||||
- **Iesire**: XML cu randurile bifate (`ales=1`), convertit local prin `Xmltocursor(...,
|
||||
"crsFacturiTemp")`; **nimic nu ajunge la Oracle in acest pas**.
|
||||
- **Populare aditiva a rezultatului in VFP**: `poDate.listaid = cursor2lista("crsFacturiTemp",
|
||||
"id_vanzare", ",")` — **inlocuieste** intreg continutul (`=`, nu `+=`); daca dialogul se apeleaza a
|
||||
doua oara azi, a doua rulare suprascrie complet prima (dar azi asta nu se intampla niciodata pentru
|
||||
ca dialogul ruleaza o singura data, inainte de orice alta actiune pe document — vezi 2.1).
|
||||
- **`cauta_alfa(...,tnTipReturn=1)`** (`COMUN\programe\cauta_alfa.prg:16-260`, mecanismul generic
|
||||
folosit peste tot in suita): adauga singur coloana `ales` (`cauta_alfa.prg:124`), titlu explicit
|
||||
"Alegeti ... (mouse-click pe numar sau apasati SPACE)", filtrare interactiva pe coloanele afisate.
|
||||
Nu are mecanism de dezactivare a unei optiuni individuale, nici submeniu — doar bifare/debifare.
|
||||
|
||||
### 2.3 Varianta per-articol
|
||||
|
||||
`caut_facturi_multiple_client_articol(tnIdPart, tnInValuta, tnIdValuta, tlFacturiMultiple, tnIdArticol)`
|
||||
(`oproceduri_facturare.prg:2124-2159`) — **aceeasi functie de baza**, cu doua diferente: filtru
|
||||
suplimentar `b.id_articol = ?pnIdArticol` (join pe `vanzari_detalii`) si `tlFacturiMultiple=.F.` la
|
||||
apelul din `do_adauga_articol` (`:12884`), deci `lnTipReturn=0` -> selectie **simpla**, nu multipla.
|
||||
Titlu simplu "Alegeti factura". Rezultatul e un obiect cu un singur `id_vanzare`, nu un cursor.
|
||||
|
||||
---
|
||||
|
||||
## 3. Proiectarea punctului 1 — alegerea facturilor sursa ca sursa in meniul de adaugare
|
||||
|
||||
**Ce NU se schimba**: alegerea propriu-zisa a facturilor sursa (dialogul cu bifare multipla) ramane
|
||||
la nivelul antetului — in formularul unificat, sectiunea pliata (decizia 7). Verificat impotriva
|
||||
tabelului "puncte marunte" din plan (`plan_13...md`, randul h): *„Alege facturile de returnat…"
|
||||
ramane doar la antet in etapa I; mutarea (in bara de butoane) e o poveste separata* — si confirmat
|
||||
independent de tabelul de meniu al lui `s4b_bara_butoane_meniu.md` §3: pe randul "retur (8,9,24)",
|
||||
meniul "Adauga articole" propus contine **"Adauga tot din facturile alese" · "Alege liniile de
|
||||
returnat…" · "Cauta in lista de preturi…" · "Alege din nomenclator…"** — nu si "Alege facturile de
|
||||
returnat…". **Punctul 1 al lui S4f nu contrazice asta**: ce se muta in meniul de adaugare e
|
||||
rezultatul alegerii deja facute la antet (liniile din facturile alese), simetric cu comanda/
|
||||
contract/avize, nu dialogul de alegere a facturilor insusi.
|
||||
|
||||
**Ce SE schimba, si e proiectarea reala**: azi secventa e rigida — antet modal -> `cursor_retur` ->
|
||||
formular de articole (sectiunea 2.1). In formularul unificat exista un singur formular, mereu
|
||||
deschis; sectiunea de antet (pliata) si gridul de articole coexista. Trei consecinte de proiectat:
|
||||
|
||||
1. **Cand se declanseaza `cursor_retur_document`?** Nu mai poate fi legat de "inchiderea antetului"
|
||||
(nu mai exista acel eveniment). Trebuie legat de **confirmarea dialogului de alegere** (butonul de
|
||||
cautare din sectiunea pliata, echivalentul lui `Ct_clb_altele`/`do_cauta_facturi`): imediat ce
|
||||
utilizatorul bifeaza facturile si confirma, `poDate.listaid` se seteaza si `cursor_retur_document`
|
||||
ruleaza pe loc, populand/repopuland `crsarticole`. Documentul poate porni cu tipul deja pe 8/9/24
|
||||
(ales din combo-ul de tip document) inainte ca vreo factura sa fie aleasa — grid-ul de articole
|
||||
ramane gol pana la prima alegere, exact ca azi cand `Reccount(crsarticole)=0` da mesajul "Nu exista
|
||||
articole pentru optiunile alese!" (`ofacturare.prg:325`).
|
||||
2. **Poate opera la orice moment, nu doar o data.** Spre deosebire de azi (dialogul ruleaza o singura
|
||||
data, inainte ca gridul sa existe), in formularul unificat butonul din sectiunea pliata e
|
||||
apasabil oricand documentul e deschis. Trebuie decis explicit comportamentul la a doua apasare —
|
||||
vezi punctul urmator.
|
||||
3. **Alegere aditiva sau inlocuitoare, la a doua apasare.** Azi `poDate.listaid = ...` **inlocuieste**
|
||||
(nu `+=`), dar azi nu conteaza pentru ca nu se poate apasa a doua oara dupa ce gridul exista. In
|
||||
formularul unificat, daca utilizatorul a ales deja factura A, a adaugat cateva linii din ea in
|
||||
`crsfactura`, si apoi vrea sa adauge si din factura B, doua variante:
|
||||
- **(a) Inlocuire** — a doua alegere reseteaza `poDate.listaid`/`crsarticole` la noul set ales;
|
||||
liniile deja mutate in `crsfactura` raman (crsfactura e independent de crsarticole dupa mutare),
|
||||
dar orice linie inca needitata din factura A dispare din `crsarticole` fara avertisment. Simplu
|
||||
de implementat (identic cu azi), dar surprinzator pentru operator.
|
||||
- **(b) Aditiv** — a doua alegere **extinde** `poDate.listaid` cu facturile noi (evitand
|
||||
duplicate) si ruleaza `cursor_retur_document` filtrat doar pe facturile noi, cu
|
||||
`INSERT INTO crsarticole` (nu `SELECT INTO`), exact reteta deja propusa de
|
||||
`s4b_bara_butoane_meniu.md` §9.3, optiunea 2 — cere cod VFP nou (bucla de filtrare +
|
||||
`INSERT`), dar zero cod nou in `pack_facturare` (aceeasi procedura, apelata de mai multe ori).
|
||||
Consistent cu filozofia deciziei 16 ("sursa umple documentul, nu il inchide").
|
||||
|
||||
**Recomandare**: varianta (b), aditiv — e coerenta cu tratamentul lista-de-preturi (S4e) si cu
|
||||
comanda/contract (unde alegerea suplimentara nu sterge ce exista deja), si evita pierderea tacuta
|
||||
de linii needitate. **Ramane decizie de confirmat de Marius** — nici plan-ul, nici
|
||||
`s4b_bara_butoane_meniu.md` nu au tranzat-o explicit (§9.5, punctul 2 acolo o listeaza tot ca
|
||||
deschisa).
|
||||
|
||||
**Consecinta pentru validarea de antet**: garda de azi (`inainte_de_do_termin`, `descriere` obligatoriu
|
||||
pentru tip 8/9, `ofacturare.vc2:9523-9526`) nu mai are un moment natural "inainte de a inchide
|
||||
antetul" — se muta la garda echivalenta din `Termina` (aceeasi verificare, alt declansator).
|
||||
|
||||
---
|
||||
|
||||
## 4. Proiectarea punctului 2 — ridicarea `But_retur` la nivel de document
|
||||
|
||||
**Ce se pastreaza din calea per-articol**: alegerea gestiunii ramane per-articol, prin
|
||||
`cursor_gestiuni_articol_retur` si `frm_articol_gest_factura` — decizia 22/N.3 spune explicit ca
|
||||
gestiunea si pretul de achizitie **se aleg**, nu se mostenesc, pentru orice linie fara factura
|
||||
sursa unica precisa (si N.2 nu are niciodata o factura "sursa unica a documentului", are cel mult
|
||||
factura aleasa pentru articolul curent). Punctul 2 nu schimba asta.
|
||||
|
||||
**Ce se schimba**: alegerea facturii sursa nu mai e per-articol (un dialog `cauta_alfa` cu selectie
|
||||
simpla la fiecare `do_retur`), ci **la nivel de document**, dupa modelul N.1 — un singur dialog cu
|
||||
selectie multipla (`caut_facturi_multiple_client`, acelasi ca la N.1, dar filtrat pe articolul
|
||||
curent doar daca ramane necesar; de decis daca filtrul pe articol dispare complet odata cu ridicarea
|
||||
la document). Consecinta directa: `poDate.listaid` (sau un camp nou, vezi mai jos) ar fi populat o
|
||||
singura data pentru intreg documentul, nu reconstruit per articol prin `caut_facturi_multiple_client_articol`.
|
||||
|
||||
**Capcana de proiectare, gasita pe cod**: `poDate.listaid` e deja **partajat** intre N.1 si N.2 azi —
|
||||
N.2 il suprascrie transient (`lnListaIdOld = poDate.listaid` ... `poDate.listaid = m.lnListaIdOld`,
|
||||
`ofacturare.vc2:12882,12892`) doar cat dureaza alegerea per-articol, apoi il restaureaza. Daca N.2
|
||||
ridicat la document incepe sa scrie **permanent** in `poDate.listaid` (ca N.1), cele doua mecanisme
|
||||
intra in conflict pe acelasi camp — un document normal (tip 1/5/7/10) cu retur ridicat la nivel de
|
||||
document ar avea `poDate.listaid` populat cu facturile de retur alese, camp care pe alte tipuri
|
||||
(comanda/contract/avize) e deja folosit cu alt sens (id-uri de comanda/contract/aviz sursa, vezi
|
||||
`ofacturare.prg:266-308`). **Nu e conflict pe tip 1/5/7/10** — acele tipuri nu folosesc azi
|
||||
`poDate.listaid` pentru altceva (sunt populate cu `cursor_preturi`, care nu ia `V_LISTAID` ca
|
||||
parametru) — deci reutilizarea e sigura tehnic, dar merita un camp cu nume propriu
|
||||
(`poDate.listaid_retur` sau similar) ca sa nu se ambiguizeze semantic ce inseamna `listaid` pe un
|
||||
document normal. **De decis la implementare**, nu blocant.
|
||||
|
||||
**Ce arata in bara de butoane / meniu (S4b)**: tabelul din `s4b_bara_butoane_meniu.md` §3 nu are
|
||||
azi un rand pentru "retur intr-o factura normala" — doar pentru documentele de retur propriu-zise
|
||||
(8,9,24). Ridicarea lui N.2 la nivel de document introduce o optiune noua in meniul "Adauga
|
||||
articole" **pentru tipurile 1,5,7,10**: "Retur de articole…" (deja anticipata in tabelul §3 al
|
||||
raportului S4b, coloana lista de preturi: "Cauta in lista de preturi… · Alege din nomenclator… ·
|
||||
**Retur de articole…**"). Aceasta optiune deschide dialogul de alegere multipla la nivel de document
|
||||
(prima data), apoi pe alegerile ulterioare acelasi comportament aditiv/inlocuitor discutat la
|
||||
sectiunea 3 se aplica identic.
|
||||
|
||||
**Ce dispare**: `caut_facturi_multiple_client_articol` (filtrul per-articol) ramane cod mort daca
|
||||
alegerea trece integral la nivel de document — sau se pastreaza ca alegere secundara optionala
|
||||
("mai am nevoie de o factura sursa doar pentru articolul X", nefolosita azi in acest scenariu). Nu
|
||||
e clar din decizia 17/N.3 daca trebuie eliminata explicit sau doar nu mai e calea principala — **de
|
||||
decis de Marius** (risc R3).
|
||||
|
||||
---
|
||||
|
||||
## 5. Proiectarea punctului 3 — liniile libere pe document de retur
|
||||
|
||||
**Corectie fata de premisa initiala a brief-ului** ("prin acelasi `APPEND FROM` ca la S4e"): S4e a
|
||||
gasit ca reteta literala a deciziei 16 (`ofacturare.prg:454-473`) **nu e sigura** pe niciun tip unde
|
||||
`crsarticole` e citit ca registru de `do_sterge`/`do_scrie_factura`, si a documentat asta explicit
|
||||
pentru retur, in tabelul sau de la §7:
|
||||
|
||||
> "**retur ca document (8,9,24)** | `crsarticole` = registru citit de `do_scrie_factura`? **Nu** —
|
||||
> cad pe `Otherwise`, fara `Sum(cantitate)`/`pnParametruAditional` | `do_sterge` ajusteaza
|
||||
> `crsarticole` pe `id_c`? **Da, partial** — `Inlist(poDate.tip,8,9,24) => Replace cantitate With
|
||||
> cantitate - poArticol.cantitate For id_c = poArticol.id_c` (`:14658-14659`, semn opus, urmareste
|
||||
> maximul returnabil, nu inchiderea) | Concluzie: **risc de coliziune `id_c` ramane, dar fara
|
||||
> poluarea `Sum`**: o linie libera adaugata literal prin `APPEND FROM` in `crsarticole` pe un
|
||||
> document de retur ar putea, la stergere, atinge tacut cantitatea maxima returnabila a unei linii
|
||||
> reale — de tratat cu aceeasi solutie (linii libere in afara `crsarticole`) si in S4f."
|
||||
> (`s4e_lista_preturi_pe_sursa.md`, §7, randul "retur ca document")
|
||||
|
||||
Solutia S4e (verificata acolo pe comanda/avize) se aplica identic aici — sectiunile de mai jos sunt
|
||||
rescrise pe ea, nu pe `APPEND FROM`.
|
||||
|
||||
### 5.1 Mecanismul corect: `APPEND BLANK` pe `crsfactura`, niciodata pe `crsarticole`
|
||||
|
||||
**Liniile libere nu intra in `crsarticole`.** Se adauga direct in `crsfactura`, prin acelasi gest deja
|
||||
in productie la `frm_facturare_articole2.But_nou1.do_adauga` (`ofacturare.vc2:17118-17122`):
|
||||
|
||||
```
|
||||
PROCEDURE do_adauga
|
||||
Select crsFactura
|
||||
APPEND BLANK
|
||||
this.grd_factura.SetFocus()
|
||||
ENDPROC
|
||||
```
|
||||
|
||||
urmat de editare inline prin `combosql` pe celula de cod/denumire (mecanismul construit de S4/S4e
|
||||
pentru cautarea filtrata pe server, `pack_facturare.cursor_preturi` cu `V_FILTRU_COD`/`V_FILTRU_DEN`).
|
||||
Optiunea de meniu "Cauta in lista de preturi…" (deja in tabelul S4b §3, pe randul retur) declanseaza
|
||||
exact acest gest — **nu** un `cursor_retur_document` suplimentar si **nu** un `APPEND FROM` peste
|
||||
`crsarticole`.
|
||||
|
||||
### 5.2 Cum se distinge o linie libera de una din factura sursa
|
||||
|
||||
**Campul corect e `crsfactura.id_c`, nu o coloana pe `crsarticole`.** O linie mostenita ajunge in
|
||||
`crsfactura` prin `do_adauga_articol`, care face `Select (lcCursor) / Scatter Name poArticol` pe
|
||||
randul ales din `crsarticole` (populat de `cursor_retur_document`, cu `id_c = ROWNUM`, pornind de la
|
||||
1) si copiaza acel `id_c` in randul nou din `crsfactura`. O linie libera, adaugata prin `APPEND BLANK`
|
||||
direct pe `crsfactura` (5.1), **nu trece niciodata prin acest `Scatter`** — `id_c` ramane la valoarea
|
||||
implicita a campului (`N(20)`, fara `Null`, deci `0`, `creeaza_facturacrs`,
|
||||
`COMUN\programe\ofacturare_comun.prg:1775-1785`, citat si verificat de S4e §3 punctul 3). **Oracle nu
|
||||
produce niciodata `id_c = 0`** (`ROWNUM` incepe la 1) — deci `crsfactura.id_c = 0` e semnalul curat,
|
||||
fara camp nou, care distinge o linie libera de una mostenita. Acelasi tipar e deja in productie pentru
|
||||
linia de discount (`ofacturare.vc2:14531-14537`, `Append Blank` fara `id_c` setat).
|
||||
|
||||
**Consecinta directa, fara cod suplimentar**: `do_sterge`, ramura de retur
|
||||
(`Case Inlist(poDate.tip,8,9,24) => Select crsarticole / Replace cantitate With cantitate -
|
||||
poArticol.cantitate For id_c = poArticol.id_c`, `ofacturare.vc2:14658-14659`) cauta in `crsarticole`
|
||||
un rand cu `id_c = poArticol.id_c`. Pentru o linie libera, `poArticol.id_c = 0`, iar `crsarticole` nu
|
||||
are niciodata un rand cu acel `id_c` — `REPLACE ... FOR` devine **no-op prin constructie**. Stergerea
|
||||
unei linii libere de pe un document de retur **nu atinge** maximul returnabil al nici unei linii
|
||||
mostenite — exact ce cerea brief-ul la punctul 5, si exact acelasi mecanism (nu doar aceeasi idee) ca
|
||||
solutia S4e pentru comanda.
|
||||
|
||||
### 5.3 Pretul de vanzare — riscul gasit (R1, ramane valabil)
|
||||
|
||||
**Nu e doar despre gestiune/pret de achizitie, si nu se rezolva prin mutarea in `crsfactura`.** Cand
|
||||
o linie ajunge la `do_alege_stoc` cu `Inlist(poDate.tip,8,9,24)` adevarat (orice linie pe un document
|
||||
de retur, indiferent de origine — conditia e pe `poDate.tip`, nu pe un flag de linie), se deschide
|
||||
`frm_articol_gest_factura`. In acel formular, la schimbarea coloanei de gestiune
|
||||
(`grd_gestiuni.RowColChange`), daca `Thisform.lRetur` e adevarat:
|
||||
|
||||
```
|
||||
If Thisform.lRetur
|
||||
poArticol.pretftva = pretv && pret DIN STOC, nu din lista de preturi
|
||||
...
|
||||
poArticol.cantitate = (-1) * cantitate
|
||||
Endif
|
||||
```
|
||||
(`ofacturare.vc2:4094-4103`)
|
||||
|
||||
Pentru o linie **mostenita** (N.1), asta e corect prin constructie — returnezi marfa la pretul cu
|
||||
care a fost vanduta. Pentru o linie **libera** (decizia 22 — articol niciodata vandut clientului, deci
|
||||
fara "pret de retur" de mostenit), suprascrierea cu pretul de stoc e o **eroare de pret**: linia
|
||||
libera ar iesi cu costul de stoc in loc de pretul din lista de preturi ales prin `combosql` la 5.1.
|
||||
|
||||
**Solutie propusa, actualizata**: `frm_articol_gest_factura` primeste un al doilea semnal, distinct de
|
||||
`tlRetur` ("esti pe flux de retur"), care sa insemne "aceasta linie are factura sursa" — derivat acum
|
||||
din `crsfactura.id_c <> 0` (5.2), nu dintr-o coloana lipsa in `crsarticole`. Ramura de suprascriere
|
||||
pret (`:4094-4103`) se activeaza doar cand acest al doilea semnal e adevarat.
|
||||
|
||||
### 5.4 Golul nou, nu tratat de S4e: punctul de intrare in dialogul de gestiune
|
||||
|
||||
**S4e nu are acest gol pentru ca nu are dialog de gestiune pe comanda/avize** — liniile libere de
|
||||
acolo se completeaza integral prin `combosql` (pret, TVA, valuta) fara sa mai treaca prin niciun
|
||||
dialog de alegere a gestiunii. Pe retur, decizia 22 cere explicit ca gestiunea si pretul de achizitie
|
||||
sa **se aleaga** pentru o linie libera — mecanismul care stie sa faca asta e
|
||||
`cursor_gestiuni_articol_retur` + `frm_articol_gest_factura`, dar acel dialog se intra azi **doar**
|
||||
din `do_alege_stoc`, apelat de `do_adauga_articol` pe baza unui `poArticol` scatter-uit dintr-un rand
|
||||
**deja incarcat in `crsarticole`** (`ofacturare.vc2:12843-12851,12891`). O linie libera adaugata prin
|
||||
`APPEND BLANK` pe `crsfactura` (5.1) **nu are** un asemenea rand sursa — exact acelasi gol pe care
|
||||
S4e l-a semnalat, netratat, pentru validarea de cantitate pe comanda (`s4e_lista_preturi_pe_sursa.md`
|
||||
§6: *"`do_verifica_articol` cere un `poArticol` scatter-uit dintr-un cursor sursa deja incarcat... o
|
||||
linie libera... nu are un asemenea rand sursa"*).
|
||||
|
||||
**Consecinta pentru S4f, mai stricta decat pe comanda**: pe comanda/avize (S4e), lipsa unui `poArticol`
|
||||
de sursa afecteaza doar validarea de cantitate (un gol de acoperit, dar linia se poate scrie si fara
|
||||
gestiune aleasa — comanda/avize nu cer gestiune diferita de cea din lista de preturi). Pe retur, lipsa
|
||||
aceluiasi `poArticol` blocheaza **si** alegerea gestiunii, care e obligatorie prin decizia 22. Trebuie
|
||||
proiectat un punct de intrare nou in `frm_articol_gest_factura`/`do_alege_stoc`, care sa porneasca de
|
||||
la randul **deja inserat in `crsfactura`** (dupa `combosql`, cu `id_articol` si cantitate deja
|
||||
completate) in loc de la un rand din `crsarticole` — practic o a doua cale de apel, cu acelasi dialog
|
||||
la capat, dar `poArticol` construit ad-hoc din `crsfactura` in loc de `Scatter` din `crsarticole`.
|
||||
Aceeasi problema structurala, aceeasi solutie propusa (poArticol construit ad-hoc) ca varianta (b) de
|
||||
la S4e §6 pentru `do_verifica_articol` — de unificat la implementare daca se poate, nu de rezolvat de
|
||||
doua ori independent.
|
||||
|
||||
### 5.5 Maximul returnabil — nu se aplica, si acum e clar de ce
|
||||
|
||||
Confirmat structural: azi maximul returnabil pe N.1 **nu e un calcul de "cat s-a mai returnat"**, e
|
||||
pur si simplu `CANTITATE` a liniei originale (`ff_...sql:4036`, `ofacturare.vc2:15236-15239`,
|
||||
`do_verifica_articol:14743-14754`) — validarea respinge doar semnul gresit (`tnCantitate >= 0` in mod
|
||||
retur), nu o depasire fata de un istoric. **Pentru o linie libera, intrebarea nici nu se pune in
|
||||
termenii de azi**: `do_verifica_articol`, ca si dialogul de gestiune (5.4), asteapta un `poArticol`
|
||||
din `crsarticole` — o linie libera nu are unul, deci nu poate trece prin comparatia de maxim existenta
|
||||
nici macar din greseala. Validarea proprie a liniei libere (cantitate/stoc, construita ad-hoc, 5.4)
|
||||
nu are nimic de comparat cu un "maxim returnabil" — verifica doar disponibilul de stoc curent, ca pe
|
||||
o linie normala de lista de preturi (acelasi gol deschis de S4e §6 pentru comanda, aceeasi solutie).
|
||||
|
||||
---
|
||||
|
||||
## 6. Efectul asupra `VANZARI_CORESP`
|
||||
|
||||
**Scrierea nu depinde de continutul liniilor, ci de alegerea facuta la antet.**
|
||||
`scrie_corespondente_vanzari(3)` (`ff_...sql:14834-14836`, apelata din `finalizeaza_factura`) scrie
|
||||
direct din `pack_facturare.clistaid` (variabila de sesiune, = `poDate.listaid` trimis la
|
||||
`initializeaza_date_factura`), **independent** de ce a ajuns efectiv in `VANZARI_DETALII` la
|
||||
finalizare:
|
||||
|
||||
```sql
|
||||
INSERT INTO VANZARI_CORESP (ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP)
|
||||
SELECT pack_facturare.nid_vanzare, ID_VANZARE, 3
|
||||
FROM VANZARI WHERE ID_VANZARE IN (SELECT X FROM table(charn2collection(V_LISTAID,',')));
|
||||
```
|
||||
(`ff_...sql:15481-15516`, citat integral in `legatura_linie_retur.md:45-53`)
|
||||
|
||||
**Consecinta directa pentru liniile libere**: daca documentul de retur contine si linii libere (din
|
||||
lista de preturi), corespondenta **tot se scrie**, si scrie exact aceleasi facturi alese la antet —
|
||||
liniile libere nu adauga si nu scot randuri din `VANZARI_CORESP`, pentru ca sursa e `poDate.listaid`
|
||||
(fixat la antet), nu continutul `crsfactura`/`VANZARI_DETALII`. Un document de retur cu 3 linii
|
||||
mostenite din factura A si 2 linii libere scrie tot un singur rand in `VANZARI_CORESP`
|
||||
(`ID_VANZARE_FACT`=documentul nou, `ID_VANZARE_AVIZ`=factura A, `TIP=3`) — corespondenta descrie
|
||||
"acest document a folosit ca sursa factura A", nu "aceste linii vin din factura A".
|
||||
|
||||
**Ce se scrie cand nu s-a ales nicio factura sursa**: nu e un caz posibil pentru documentul de retur
|
||||
insusi — validarea de antet (`inainte_de_do_termin`, `descriere` obligatoriu, sectiunea 3) impune cel
|
||||
putin o factura aleasa inainte ca documentul sa poata continua. Deci pentru N.1/S4f (documente 8/9/24)
|
||||
`clistaid` nu e niciodata gol la scriere. **Pentru N.2 ridicat la document (punctul 2)** raspunsul e
|
||||
diferit: acolo documentul e de tip 1/5/7/10, iar `poDate.listaid`/campul nou echivalent (sectiunea 4)
|
||||
nu e obligatoriu — un document normal fara nicio linie de retur nu are ce sa scrie. **Intrebare
|
||||
deschisa, verificata partial**: daca `scrie_corespondente_vanzari(3)` e apelata azi doar cand
|
||||
`ntip in (8,9)` (confirmat de `legatura_linie_retur.md:113`: *"TIP=3: factura de retur
|
||||
(scrie_corespondente_vanzari(3), :14834-14836, la ntip in (8,9))"*), atunci **ridicarea lui N.2 la
|
||||
nivel de document NU va scrie automat `VANZARI_CORESP`** — documentul ramane tip 1/5/7/10, gardat de
|
||||
`ntip`, nu de continutul `listaid`. Linia exacta a acestui `IF`/`CASE` in exportul curent
|
||||
(`ff_2026_08_09_01...sql`) **nu a fost re-verificata caracter cu caracter** in aceasta sesiune (citata
|
||||
din raportul sursa, care a folosit un export anterior cu alta numerotare) — de reverificat la
|
||||
implementare, dar concluzia structurala (gating pe `ntip`, nu pe `listaid`) e solida.
|
||||
|
||||
**Decizie de proiectare care rezulta**: ridicarea lui N.2 la nivel de document (punctul 2) **nu
|
||||
capata**, prin simpla ridicare, o corespondenta persistata in `VANZARI_CORESP` — comportamentul
|
||||
raman identic cu azi (nicio legatura scrisa), *cu exceptia* cazului in care Marius decide explicit ca
|
||||
merita cod Oracle nou (extinderea gardei `ntip in (8,9)` la un al treilea semnal, ex. "are `listaid`
|
||||
populat"). Decizia 35 (un singur cod de scriere contabila, cod Oracle nou doar cand strict necesar)
|
||||
face ca varianta "fara schimbare in pack_facturare" sa fie implicita — **de confirmat cu Marius**
|
||||
(risc R4), nu de presupus.
|
||||
|
||||
---
|
||||
|
||||
## 7. Semnul cantitatii / al valorii pe retur
|
||||
|
||||
**Doua mecanisme diferite, verificate separat pe cod:**
|
||||
|
||||
- **N.1 (documente 8/9/24)**: cantitatea vine deja cu semnul corect direct din
|
||||
`cursor_retur_document` — `CANTITATE` a liniei originale (pozitiva ca valoare de referinta, dar
|
||||
interpretata cu semn de retur prin `poDate.tip`); semnul efectiv de scriere in `VANZARI_DETALII`
|
||||
e controlat de codul Oracle care insereaza documentul de retur (nu urmarit aici, in afara
|
||||
perimetrului VFP), iar in UI validarea `do_verifica_articol` (`llRetur = Inlist(poDate.tip,8,9,24)`)
|
||||
respinge `tnCantitate >= 0` in mod retur — deci utilizatorul introduce cantitatea ca valoare
|
||||
pozitiva ("cat returnez"), iar semnul negativ e o conventie aplicata la nivel de tip de document,
|
||||
nu per linie.
|
||||
- **N.2 (`But_retur`)**: semnul negativ **e aplicat explicit in cod VFP**, in
|
||||
`frm_articol_gest_factura` — `poArticol.cantitate = (-1) * cantitate` (`ofacturare.vc2:4103`),
|
||||
declansat de `Thisform.lRetur` (adevarat cand `tlRetur` a fost trimis la `Init`, indiferent de
|
||||
`poDate.tip`). Aici semnul **nu** vine din `poDate.tip` (documentul e tip 1/5/7/10, pozitiv prin
|
||||
design), ci e o negatie explicita facuta pentru ca acea linie anume e un retur intr-un document
|
||||
altfel normal.
|
||||
|
||||
**Implicatie pentru liniile libere pe document de retur (S4f punctul 3)**: pentru ca documentul
|
||||
intreg e tip 8/9/24, orice linie — libera sau mostenita — trece prin acelasi tratament de tip de
|
||||
document (semnul negativ e o proprietate a **documentului**, nu a liniei, in acest caz). Nu exista
|
||||
risc de "linie libera cu semn pozitiv gresit" atata timp cat validarea ramane gatata pe `poDate.tip`
|
||||
si nu pe originea liniei — de verificat explicit ca ramura noua a validarii (5.4, sarirea peste
|
||||
maximul returnabil) **nu** atinge si sare peste verificarea de semn, care trebuie sa ramana activa
|
||||
identic pe linii libere si mostenite.
|
||||
|
||||
**Implicatie pentru N.2 ridicat la document (punctul 2)**: aici semnul **ramane** o proprietate de
|
||||
linie (nu de document, pentru ca documentul e tip normal 1/5/7/10) — mecanismul de la
|
||||
`ofacturare.vc2:4103` se pastreaza neschimbat, declansat de acelasi `tlRetur`/`Thisform.lRetur`,
|
||||
indiferent daca alegerea facturii sursa se face per-articol (ca azi) sau la nivel de document
|
||||
(punctul 2). Nimic de schimbat aici — doar de confirmat ca noul flux (alegere la document) tot
|
||||
seteaza `tlRetur=.T.` la apelul catre `do_alege_stoc`/`frm_articol_gest_factura` pentru fiecare linie
|
||||
de retur adaugata.
|
||||
|
||||
---
|
||||
|
||||
## 8. Unde se aseaza informatia de provenienta in UI
|
||||
|
||||
**Inchis pe cod, cf. `legatura_linie_retur.md`**: legatura linie-la-linie nu se poate afisa in niciun
|
||||
caz — nici `cursor_retur_document` nu aduce `ID_VANZARE`/`ID_VANZARE_DET` in `crsarticole`
|
||||
(`ff_...sql:3965-4028`, coloanele lipsesc din SELECT), nici `INSERT`-ul final in `VANZARI_DETALII`
|
||||
nu are coloana de sursa (`PACK_FACTURARE:13705-13757`, listata explicit, fara camp de provenienta).
|
||||
|
||||
**Ce se poate afisa, verificat**: la nivel de **document**, cand exact o singura factura sursa a fost
|
||||
aleasa, `VANZARI_CORESP(TIP=3)` da fara ambiguitate factura sursa:
|
||||
|
||||
```sql
|
||||
SELECT v.serie_act, v.numar_act
|
||||
FROM VANZARI_CORESP c JOIN VANZARI v ON v.ID_VANZARE = c.ID_VANZARE_AVIZ
|
||||
WHERE c.ID_VANZARE_FACT = :id_document_retur AND c.TIP = 3 AND c.STERS = 0;
|
||||
```
|
||||
(`legatura_linie_retur.md:174-178`, interogare formulata, neverificata pe date live in acel raport)
|
||||
|
||||
**Propunere concreta de text si conditie** (in romana, pentru sectiunea pliata / antetul
|
||||
formularului unificat):
|
||||
|
||||
- **O singura factura sursa aleasa**: eticheta fixa de tip "Provine din factura: **{serie} {numar}**"
|
||||
langa campul de descriere existent (`poDate.text_aditional`, deja populat azi cu
|
||||
"RETUR FACTURA {numere}" — `factura_retur_document.md:65`), afisata read-only, imediat sub sau langa
|
||||
campul de tip document.
|
||||
- **Mai multe facturi sursa alese**: eticheta devine "Provine din facturile: **{lista serie+numar}**"
|
||||
— aceeasi sursa de date (`poDate.descriere`, deja populata cu lista, nu doar `VANZARI_CORESP`),
|
||||
**fara** sa se sugereze vreo distributie pe linii.
|
||||
- **Niciodata pe linie** — nici coloana in grid, nici tooltip per rand care sa pretinda o sursa
|
||||
exacta. Pentru liniile libere in particular, nu exista nimic de afisat (nu au sursa) — nu se
|
||||
introduce o valoare "N/A" vizibila care ar sugera ca alte linii AU o sursa unica atunci cand de
|
||||
fapt (cu selectie multipla) nici ele nu o au cu certitudine.
|
||||
- **Conditie tehnica**: textul se construieste din `poDate.descriere` (deja in memorie, fara interogare
|
||||
suplimentara) — nu necesita interogarea `VANZARI_CORESP` de mai sus decat daca se doreste
|
||||
reafisarea la redeschiderea unui document deja emis (regenerare, etapa II) — caz in care
|
||||
interogarea de mai sus e necesara pentru ca `poDate.descriere` nu mai exista in memorie.
|
||||
|
||||
---
|
||||
|
||||
## 9. Pasi de implementare ordonati
|
||||
|
||||
1. **Confirmarea comportamentului aditiv/inlocuitor la alegerea facturilor sursa** (sectiunea 3,
|
||||
punctul 3) — decizie de produs, nu cod. *Gata cand:* Marius a ales (a) sau (b); fara asta pasii 2-3
|
||||
nu se pot implementa fara risc de refacere.
|
||||
*Verificabil:* niciunul (decizie, nu cod).
|
||||
2. **Butonul/actiunea de alegere a facturilor sursa, mutat in sectiunea pliata a formularului
|
||||
unificat**, pastrand `do_cauta_facturi`/`caut_facturi_multiple_client` neschimbate — doar
|
||||
punctul de declansare se muta din antetul modal in sectiunea pliata a formularului unic, si
|
||||
`cursor_retur_document` se declanseaza la confirmarea dialogului, nu la inchiderea unui formular
|
||||
separat (sectiunea 3, punctele 1-2).
|
||||
*Gata cand:* pe un document nou de tip 8/9/24, deschis in formularul unificat, alegerea facturilor
|
||||
sursa din sectiunea pliata populeaza `crsarticole` cu exact aceleasi randuri ca azi (gestiune, pret
|
||||
achizitie, pret vanzare identice), verificabil prin comparatie directa a cursorului.
|
||||
3. **Meniul "Adauga articole" pe tipurile 8/9/24**, cu optiunile "Adauga tot din facturile alese" /
|
||||
"Alege liniile de returnat…" peste `crsarticole` deja populat (reteta comuna S4b, sectiunile 4-5
|
||||
ale acelui raport) — fara alegere de facturi in acest meniu (sectiunea 3).
|
||||
*Gata cand:* meniul contine exact aceste doua optiuni pe tip 8/9/24, plus "Cauta in lista de
|
||||
preturi…"/"Alege din nomenclator…" (punctul urmator).
|
||||
4. **Liniile libere — `APPEND BLANK` pe `crsfactura`, semnalul de distinctie pe `id_c`** (sectiunile
|
||||
5.1-5.2): optiunea de meniu "Cauta in lista de preturi…" pe tip 8/9/24 declanseaza
|
||||
`Select crsFactura / APPEND BLANK` + `combosql`, exact tiparul S4e (`ofacturare.vc2:17118-17122`)
|
||||
— **nu** `APPEND FROM` peste `crsarticole`. `id_c` ramane implicit (`0`) pe randul nou.
|
||||
*Gata cand:* dupa adaugarea unei linii libere pe un document de test, `crsfactura.id_c = 0` pe acel
|
||||
rand si `crsarticole` ramane neschimbat (acelasi `Reccount`/continut ca inainte de adaugare).
|
||||
5. **Punct de intrare nou in dialogul de gestiune, pornind de la randul din `crsfactura`** (sectiunea
|
||||
5.4) — `frm_articol_gest_factura`/`do_alege_stoc` primesc o a doua cale de apel, cu `poArticol`
|
||||
construit ad-hoc din randul deja inserat in `crsfactura` (dupa `combosql`), nu din `Scatter` pe un
|
||||
rand din `crsarticole`. Fara acest pas, o linie libera nu poate ajunge deloc la alegerea gestiunii
|
||||
ceruta de decizia 22.
|
||||
*Gata cand:* pe o linie libera adaugata pe un document de retur, dialogul de gestiune se deschide si
|
||||
returneaza `ID_GESTIUNE`/`PRET_ACHIZITIE` alese de operator, fara sa fi trecut prin `crsarticole`.
|
||||
6. **Fixarea pretului de vanzare pe liniile libere** (sectiunea 5.3, riscul R1) — ramura de
|
||||
suprascriere din `frm_articol_gest_factura` (`ofacturare.vc2:4094-4103`) primeste al doilea semnal
|
||||
("are sursa" vs. "e libera"), derivat din `crsfactura.id_c <> 0` (pasul 4), si nu suprascrie
|
||||
`pretftva` cand linia e libera.
|
||||
*Gata cand:* o linie libera adaugata pe un document de retur pastreaza pretul din lista de preturi
|
||||
dupa alegerea gestiunii, verificabil comparand `poArticol.pretftva` inainte si dupa
|
||||
`RowColChange` in dialogul de gestiune.
|
||||
7. **Validarea de cantitate/stoc pe liniile libere** (sectiunea 5.5) — mecanism nou, nu
|
||||
`do_verifica_articol` ca atare (care cere un `poArticol` din `crsarticole`); verifica disponibilul
|
||||
de stoc curent, fara nicio comparatie cu un maxim returnabil. Unificabil cu golul echivalent
|
||||
deschis de S4e §6 pentru comanda/avize, daca implementarea alege aceeasi varianta (`poArticol`
|
||||
construit ad-hoc, parametru explicit).
|
||||
*Gata cand:* pe o linie libera, orice cantitate <= stocul curent trece validarea, cu mesaj propriu
|
||||
(nu "cantitate maxima de returnat"); pe o linie mostenita, comportamentul de azi ramane neschimbat.
|
||||
8. **Ridicarea `But_retur` la nivel de document (punctul 2)** — dialog de alegere multipla la
|
||||
deschiderea primei linii de retur pe un document normal (1/5/7/10), populare cu un camp nou sau
|
||||
reutilizarea controlata a lui `poDate.listaid` (sectiunea 4), pastrand calea per-articol existenta
|
||||
ca fallback sau eliminand-o explicit (decizie separata, risc R3).
|
||||
*Gata cand:* pe o factura normala, alegerea facturilor sursa se face o singura data, iar liniile de
|
||||
retur adaugate ulterior (mai multe articole) folosesc acel set fara sa redeschida dialogul de
|
||||
cautare la fiecare articol.
|
||||
9. **`VANZARI_CORESP` pentru N.2 ridicat la document** (sectiunea 6) — de decis daca se adauga cod
|
||||
Oracle nou sau ramane fara corespondenta persistata (risc R4); implementat doar dupa decizia lui
|
||||
Marius.
|
||||
*Gata cand:* comportamentul ales (cu sau fara corespondenta) e implementat consistent si testat pe
|
||||
un document cu retur ridicat la nivel de document si mai multe facturi sursa.
|
||||
10. **Textul de provenienta la antet** (sectiunea 8) — afisare read-only din `poDate.descriere`,
|
||||
condusa de numarul de facturi alese (0/1/multiple).
|
||||
*Gata cand:* eticheta arata corect in toate trei cazurile (nicio factura inca, o factura, mai
|
||||
multe), fara sa apara pe grid/linie.
|
||||
|
||||
*Depinde de:* S2 (formular unificat de baza), S4e (mecanismul `APPEND BLANK` + `combosql` pentru
|
||||
liniile libere pe document cu sursa, NU `APPEND FROM`) — exact ca in plan (`plan_13...md:2330`),
|
||||
corectat fata de reteta literala a deciziei 16.
|
||||
|
||||
---
|
||||
|
||||
## 10. Cum se verifica
|
||||
|
||||
**Proba din plan** (`plan_13...md:2327-2329`): *"un document de retur deschis in formularul unificat
|
||||
aduce liniile facturilor alese cu aceleasi valori ca azi — inclusiv gestiunea si pretul de
|
||||
achizitie —, permite stergere si cantitate partiala, iar pe o factura normala se poate face retur
|
||||
alegand facturile o singura data."*
|
||||
|
||||
Pasi concreti:
|
||||
|
||||
1. **Paritate N.1**: pe date de test, emite un document de retur (tip 8) pe calea veche (formularul
|
||||
actual) si retine `crsarticole` rezultat (gestiune, pret achizitie, pret vanzare, cantitate per
|
||||
linie). Repeta aceeasi alegere de facturi sursa in formularul unificat si compara `crsarticole`
|
||||
camp cu camp — trebuie sa fie identic, pentru ca sursa Oracle (`cursor_retur_document`) nu se
|
||||
schimba, doar punctul VFP care o declanseaza.
|
||||
2. **Stergere si retur partial**: pe formularul unificat, sterge o linie mostenita si verifica ca
|
||||
cantitatea reintra in `crsarticole` (acelasi test ca azi, `do_sterge` neschimbat); introdu o
|
||||
cantitate mai mica decat maximul pe o linie mostenita si verifica validarea.
|
||||
3. **Linie libera**: adauga o linie din lista de preturi pe documentul de retur deschis (prin
|
||||
`APPEND BLANK` + `combosql`, nu `APPEND FROM`); verifica (a) `crsfactura.id_c = 0` pe acel rand si
|
||||
`crsarticole` neschimbat; (b) pretul de vanzare = pretul din lista de preturi, nu din stoc; (c)
|
||||
gestiunea/pretul de achizitie sunt cerute prin dialogul de gestiune (intrat pe calea noua, 5.4), nu
|
||||
completate automat; (d) cantitatea nu e limitata de niciun maxim istoric, doar de stocul curent;
|
||||
(e) semnul cantitatii ramane negativ (consistent cu documentul); (f) stergerea liniei libere nu
|
||||
modifica `crsarticole` (verifica direct: `Reccount`/continut identic inainte si dupa stergere).
|
||||
4. **`VANZARI_CORESP` neschimbat de liniile libere**: dupa emiterea documentului din pasul 3,
|
||||
interogheaza:
|
||||
```sql
|
||||
SELECT ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP FROM VANZARI_CORESP
|
||||
WHERE ID_VANZARE_FACT = :id_document_nou AND TIP = 3;
|
||||
```
|
||||
— trebuie sa arate exact facturile alese la antet, indiferent de cate linii libere s-au adaugat.
|
||||
5. **N.2 ridicat la document**: pe o factura normala, adauga doua linii de retur pentru articole
|
||||
diferite folosind acelasi set de facturi sursa ales o singura data (nu redeschide dialogul la
|
||||
fiecare articol); verifica semnul cantitatii (negativ) si absenta oricarei scrieri in
|
||||
`VANZARI_CORESP` (daca decizia de la punctul 8/risc R4 pastreaza comportamentul de azi).
|
||||
6. **Provenienta**: deschide un document de retur cu o singura factura sursa — verifica textul de
|
||||
antet; alege doua facturi sursa — verifica ca textul listeaza ambele si nu implica o sursa unica.
|
||||
|
||||
## 11. Ce nu se poate testa headless
|
||||
|
||||
Reluat din capcanele deja confirmate pe acest proiect (`s4b_bara_butoane_meniu.md` §8), plus
|
||||
specificul retur:
|
||||
|
||||
- **`cauta_alfa` cu `tnTipReturn=1`** — dialog modal (`.Show(1)`), blocheaza thread-ul UI; testarea
|
||||
automata a bifarii multiple nu se poate face prin injectare de input pe aceasta masina (partajata,
|
||||
memoria `masina-partajata-fara-input-real`). Se verifica indirect: apeland direct
|
||||
`caut_facturi_multiple_client`/`cursor_retur_document` cu parametri de test si citind cursorul
|
||||
rezultat, ocolind UI-ul.
|
||||
- **`frm_articol_gest_factura`** — acelasi tip de dialog modal; verificarea suprascrierii de pret
|
||||
(risc R1, sectiunea 5.3) si a noului punct de intrare pornit din `crsfactura` (sectiunea 5.4) se
|
||||
poate face doar citind codul metodei `RowColChange`/noua metoda de intrare si, separat, apeland
|
||||
direct logica de calcul cu date de test simulate — nu prin click real in grid.
|
||||
- **Coloanele griduri** (`grd_articole`, gridul dialogului de alegere) — capcana deja cunoscuta:
|
||||
`ColumnCount=0` sub `-A -T`; verificare vizuala necesita harness UI vizibil, nu headless.
|
||||
(memoria `grid-coloane-nu-se-materializeaza-headless`)
|
||||
- **Textul de provenienta din sectiunea pliata** (sectiunea 8) — verificabil headless ca **continut
|
||||
al proprietatii** (`Caption`/`Value` a controlului), nu ca "arata bine" vizual.
|
||||
- **Secventa reala de deschidere a dialogului din sectiunea pliata** (declansarea la click, nu doar
|
||||
codul din spatele ei) — necesita harness UI vizibil pentru captura de interactiune.
|
||||
|
||||
**Ce se poate verifica headless, direct**: continutul `crsarticole`/`crsfactura` dupa apeluri directe
|
||||
ale rutinelor (`cursor_retur_document`, `do_adauga_articol`, `do_verifica_articol`, `APPEND BLANK` pe
|
||||
`crsfactura`) cu parametri de test; valoarea `crsfactura.id_c` pe randurile noi (semnalul de la 5.2,
|
||||
`0` pe liniile libere, valoare reala pe cele mostenite); continutul real din
|
||||
`VANZARI_CORESP`/`VANZARI_DETALII` dupa emiterea unui document de test pe schema Oracle (doar
|
||||
`SELECT`, verificare post-factum).
|
||||
|
||||
---
|
||||
|
||||
## 12. Riscuri si ce ramane de decis de Marius
|
||||
|
||||
**R1 — Pretul de vanzare pe liniile libere se suprascrie cu pretul de stoc, daca nu se trateaza
|
||||
explicit** (sectiunea 5.3). Cel mai concret risc gasit in aceasta cercetare, netratat in plan pana
|
||||
acum. **Recomandare**: al doilea semnal ("linie cu sursa" vs. "linie libera") pe langa `tlRetur`,
|
||||
derivat din `crsfactura.id_c <> 0` (5.2), care sa dezactiveze suprascrierea de pret doar pentru
|
||||
liniile libere. Necesita o mica modificare in `frm_articol_gest_factura` (nu in `pack_facturare`).
|
||||
|
||||
**R7 — Punctul de intrare in dialogul de gestiune pentru o linie libera nu exista azi** (sectiunea
|
||||
5.4). Gol nou, mai strict decat echivalentul lui pe comanda/avize (S4e §6, doar validare de
|
||||
cantitate) — pe retur blocheaza si alegerea gestiunii/pretului de achizitie, obligatorie prin decizia
|
||||
22. `do_alege_stoc`/`frm_articol_gest_factura` se intra azi doar dintr-un `poArticol` scatter-uit din
|
||||
`crsarticole`; o linie libera (`APPEND BLANK` pe `crsfactura`, 5.1) nu are asa ceva. **Recomandare**:
|
||||
a doua cale de apel, cu `poArticol` construit ad-hoc din randul `crsfactura` deja completat prin
|
||||
`combosql`, unificata daca se poate cu solutia aleasa pentru golul echivalent de validare (R7 aici,
|
||||
S4e §6/varianta b acolo) — un singur mecanism de "construieste `poArticol` fara sursa in `crsarticole`",
|
||||
nu doua independente.
|
||||
|
||||
**R2 — Alegere aditiva sau inlocuitoare la a doua apasare a dialogului de facturi sursa** (sectiunea
|
||||
3, punctul 3). Nu e tratat de nicio decizie existenta. **Recomandare**: aditiv, consistent cu
|
||||
filozofia deciziei 16, dar cere cod VFP nou (bucla de filtrare pe facturi noi + `INSERT INTO
|
||||
crsarticole`).
|
||||
|
||||
**R3 — Ce se intampla cu `caut_facturi_multiple_client_articol` (alegerea per-articol) dupa ridicarea
|
||||
lui `But_retur` la nivel de document.** Ramane ca optiune secundara sau se elimina? Nefolosit oriunde
|
||||
altundeva in cod (verificat implicit — apelul e doar din `do_adauga_articol`, ramura `tlRetur`).
|
||||
**Recomandare**: se elimina, pentru ca pastrarea ambelor cai ar insemna doua UX-uri pentru aceeasi
|
||||
actiune, exact riscul pe care unificarea vrea sa il elimine.
|
||||
|
||||
**R4 — `VANZARI_CORESP` pentru N.2 ridicat la document.** Gardat azi pe `ntip in (8,9)`, nu pe
|
||||
continutul `listaid` — **reconfirmat independent** in aceasta sesiune, pe exact acelasi export
|
||||
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:14834-14836`): `WHEN pack_facturare.ntip in (8, 9) THEN
|
||||
pack_facturare.scrie_corespondente_vanzari(3);`, in interiorul unui `CASE` fara alta conditie pe
|
||||
`listaid`. Daca ramane asa, un document normal
|
||||
cu retur ridicat la document nu va avea nicio legatura persistata catre facturile sursa alese — exact
|
||||
ca azi cu `But_retur` per-articol, deci nu e o regresie, dar nici un castig. **Recomandare**: se lasa
|
||||
neschimbat (fara cod Oracle nou), consistent cu decizia 35 (economie de cod de intretinut) — de
|
||||
confirmat explicit cu Marius, pentru ca punctul 2 al S4f ("ridicarea la nivel de document") ar putea
|
||||
crea asteptarea implicita ca acum exista si o legatura persistata, cand de fapt nu exista.
|
||||
|
||||
**R5 — `Ct_clb_altele.cconditie` — semantica exacta neconfirmata** (sectiunea 2.1). Daca acest camp
|
||||
chiar dezactiveaza azi re-alegerea dupa prima selectie, design-ul nou (sectiunea 3) trebuie sa decida
|
||||
explicit sa **elimine** aceasta restrictie (pentru varianta aditiva) — altfel comportamentul nou ar
|
||||
fi mai permisiv decat parea sa fie azi, fara sa fi fost o decizie constienta. **De verificat la
|
||||
implementare**, cand clasa `ct_clb_cautare` devine accesibila (posibil in `COMUNROA`, in afara acestui
|
||||
repo).
|
||||
|
||||
**R6 — Semnul cantitatii pe liniile libere, verificare explicita ceruta de brief** (sectiunea 7): nu
|
||||
e un risc nou — analiza arata ca semnul e o proprietate a documentului (tip 8/9/24), nu a liniei,
|
||||
deci liniile libere il mostenesc automat corect. Se listeaza aici doar ca sa fie explicit ca punctul
|
||||
a fost verificat, nu omis.
|
||||
|
||||
---
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare incheiata fara sa fie nevoie de predare de context — un subagent de verificare
|
||||
(`ab0be8e27f0fb97f3`, Explore/general-purpose) a esuat de doua ori sa returneze rezultate, apoi a
|
||||
raspuns complet la a treia incercare (reluat prin `SendMessage`), confirmand independent 4 puncte deja
|
||||
scrise pe cod propriu in raport: (1) `do_cauta_facturi` e declansat explicit de user, prin
|
||||
`Ct_clb_altele`/`caut_ora.vc2:787-798,839-844` -> `do_cauta_altele` -> `do_cauta_facturi`
|
||||
(`ofacturare.vc2:8940-8961,9173`), inainte de orice deschidere a `frm_facturare_articole`; (2)
|
||||
`scrie_corespondente_vanzari(3)` e gardat exact pe `ntip in (8,9)`
|
||||
(`ff_...sql:14834-14836`) — folosit sa inchid definitiv R4; (3)
|
||||
`thisform.cListaIdArticoleRetur` poate acumula `ID_VANZARE` diferit per articol
|
||||
(`ofacturare.vc2:12882-12892`); (4) `poDate.listaid` e acelasi camp la N.1 si N.2, dar cu rol
|
||||
tranzitoriu la N.2 (salvat/restaurat) si definitiv doar la `do_scrie_articole`
|
||||
(`ofacturare.vc2:13977-13979`). Niciuna din aceste confirmari nu a schimbat vreo concluzie a
|
||||
raportului, doar le-a intarit sursa.
|
||||
|
||||
**Corectie aplicata dupa livrare**, la avertismentul `team-lead` (bazat pe
|
||||
`docs\cercetare\s4e_lista_preturi_pe_sursa.md`, sectiunile 3 si 7): sectiunea 5 (liniile libere) a
|
||||
fost **rescrisa integral** — reteta `APPEND FROM` peste `crsarticole` (premisa initiala a brief-ului)
|
||||
e inlocuita cu mecanismul validat de S4e (`APPEND BLANK` pe `crsfactura` + `combosql`, niciodata pe
|
||||
`crsarticole`), campul de distinctie devine `crsfactura.id_c = 0` (nu absenta `PRET_ACHIZITIE` pe
|
||||
`crsarticole`), si s-a adaugat un gol nou, negasit de S4e (care nu are dialog de gestiune pe
|
||||
comanda/avize): punctul de intrare in `frm_articol_gest_factura` pentru o linie fara rand sursa in
|
||||
`crsarticole` (risc R7, nou). Verdictul de la inceputul raportului, pasii de implementare (9),
|
||||
verificarea (10) si sectiunile headless (11) au fost actualizate in consecinta. Restul raportului
|
||||
(sectiunile 1-4, 6-8, 12 minus R1/R7) ramane neschimbat fata de livrarea initiala.
|
||||
|
||||
Fisierul SQL folosit: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(cel mai recent din director). Nicio editare de cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun
|
||||
commit, nicio scriere Oracle.
|
||||
437
docs/cercetare/s4g_adaugare_articole_modificare.md
Normal file
437
docs/cercetare/s4g_adaugare_articole_modificare.md
Normal file
@@ -0,0 +1,437 @@
|
||||
# S4g — Adaugarea de articole la modificarea oricarui document (proiectare)
|
||||
|
||||
Proiectare, nu implementare. Zero fisiere de cod atinse, zero write-back, zero commit. Pe Oracle
|
||||
doar `SELECT`. Continua planul `docs\plan_13_unificare_formular_facturare.md` liniile 2494-2530 si
|
||||
cercetarile `canal_cont_venit_fara_politica.md` (sectiunea "Proiectarea parametrului de cont
|
||||
contabil"), `parametru_cont_contabilizeaza_articol.md`, `coresp_cont_venchelt.md`,
|
||||
`optiune_firma_cont_debit.md`.
|
||||
|
||||
**STARE: cercetare/proiectare incheiata.**
|
||||
|
||||
Sursa PL/SQL verificata direct in aceasta runda: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(17217 linii, confirmat `wc -l`). Cod VFP citit: `COMUN\clase\ofacturare.vc2`,
|
||||
`COMUN\programe\ofacturare_comun.prg` (ambele permise). Neatinse: `COMUN\clase\ofacturare_comun.vc2`,
|
||||
`COMUN\programe\ofacturare_editare.prg` (perimetrul sesiunii #6).
|
||||
|
||||
---
|
||||
|
||||
## 0. Premisele deja decise (nu se redeschid)
|
||||
|
||||
- Povestea: a doua sursa de articole in meniu, "Alege din nomenclator...", disponibila si la
|
||||
modificarea oricarui document deja emis, inclusiv auto (tip = -12, dar livrat separat, dupa).
|
||||
- Decizia 20: din nomenclator se ofera si gestionabile, si negestionabile — nu se copiaza filtrul
|
||||
`in_stoc = 0` al lui ROAAUTO.
|
||||
- Decizia 21: contul de gestiune are fallback, nu NULL, nu refuz — mecanism separat de cel de aici
|
||||
(priveste `id_gestiune`/`Cont` de gestiune al liniei, nu contul de venit), presupus deja rezolvat
|
||||
cand derivarea din sectiunea 3 porneste.
|
||||
- Decizia 27 (transportul inlocuit de decizia 34): contul de venit se **deriva** in VFP —
|
||||
`CORESP_CONT_VENCHELT` pentru gestionabile (pe contul de gestiune al liniei), `NOM_ARTICOLE.CONT`
|
||||
daca e 6xx/7xx pentru negestionabile, altfel `704`.
|
||||
- Decizia 34: transportul e parametru nou, direct — nu prin `id_pol`/politica tehnica. Ocolul prin
|
||||
`pack_preturi.adauga_politica_pret_art` e abandonat.
|
||||
- Decizia 35: acelasi drum serveste si editarea prin regenerare — nu se proiecteaza a doua ruta de
|
||||
contare.
|
||||
- Decizia 36 (noua, 10.08.2026): `SCD` pe ramura fara politica nu e hardcodat `'4111'` literal —
|
||||
vine dintr-o optiune de firma (`getoptiunefirma`), cu `4111` ca implicit dublu (rand in `OPTIUNI`
|
||||
+ fallback hardcodat in PL/SQL), tiparul deja folosit de `RF_CONT_INCASARE_*` in acelasi pachet.
|
||||
- "Alte servicii" ROAAUTO ocoleste complet `contabilizeaza_articol` — exceptie, nu jumatate de
|
||||
mecanism.
|
||||
- Regula deja adoptata in #13: liniile libere nu intra deloc in `crsarticole` — `APPEND BLANK` +
|
||||
`combosql` direct in `crsfactura`, ca `id_c` sa ramana `0` si `do_sterge` sa fie no-op prin
|
||||
constructie (verificat aici ca acelasi tipar se aplica si campului `id_pol`, sectiunea 4).
|
||||
- Ordine: intai ROAFACTURARE, apoi tip = -12.
|
||||
|
||||
---
|
||||
|
||||
## 1. Punctul central: parametru vs politica, cine castiga
|
||||
|
||||
### 1.1 Ce face azi codul, verificat direct pe sursa (nu preluat din rapoarte)
|
||||
|
||||
`contabilizeaza_articol` (`ff_...:7173-7547`) primeste un singur parametru,
|
||||
`detalii_articol VANZARI_DETALII_TEMP%ROWTYPE`. Primul lucru pe care il face (`:7275-7302`):
|
||||
|
||||
```sql
|
||||
BEGIN
|
||||
SELECT COMPUS, ID_POL_ART
|
||||
INTO V_COMPUS, V_ID_POL_ART
|
||||
FROM VCRM_POLITICI_PRET_ART
|
||||
WHERE ID_ARTICOL = detalii_articol.id_articol
|
||||
AND ID_POL = detalii_articol.id_pol;
|
||||
EXCEPTION
|
||||
WHEN NO_DATA_FOUND THEN
|
||||
...
|
||||
RAISE_APPLICATION_ERROR(-20000, 'Articolul ... nu este definit in politica de preturi ... (FACT-024)');
|
||||
END;
|
||||
```
|
||||
|
||||
Daca `detalii_articol.id_pol` e `NULL`, comparatia `ID_POL = NULL` nu se potriveste niciodata —
|
||||
`NO_DATA_FOUND` garantat, FACT-024 garantat. Acelasi rezultat daca `id_pol` e populat dar articolul
|
||||
nu e in acea politica. **`cursor_articol`** (`:7218-7271`), care face tot lucrul real (`scrie_nota`,
|
||||
`descarca_gestiune`, discount), e filtrat **pe exact aceeasi cheie** (`A.ID_POL = detalii_articol.id_pol
|
||||
AND A.ID_ARTICOL = detalii_articol.id_articol`, `:7270-7271`) — daca `SELECT INTO` de mai sus a picat
|
||||
in `NO_DATA_FOUND`, cursorul ar gasi tot zero randuri (aceeasi cauza). Deci punctul de intrare in
|
||||
politica e unic si dublu-verificat (o data prin exceptie explicita, o data structural prin cursor).
|
||||
|
||||
Design-ul deja facut (`canal_cont_venit_fara_politica.md:132-138`) propune sa infasoare **toata
|
||||
functia** intr-un switch la nivel de metoda:
|
||||
|
||||
```
|
||||
IF detalii_articol.cont_venit IS NOT NULL THEN
|
||||
<ramura noua: SCC := cont_venit, SCD := optiune de firma (decizia 36), fara cursor, fara SELECT INTO de mai sus>
|
||||
ELSE
|
||||
<tot codul de azi, neschimbat, inclusiv SELECT INTO + FACT-024 + cursor_articol>
|
||||
END IF;
|
||||
```
|
||||
|
||||
**Aceasta e deja, prin constructie, varianta "parametrul castiga necondiionat cand e populat"** —
|
||||
cand `cont_venit` nu e `NULL`, executia nu se mai uita deloc la `id_pol`/politica, indiferent daca
|
||||
acestea ar fi rezolvat sau nu. Team-lead-ul cere explicit sa se decida asta ca punct de proiectare,
|
||||
nu sa ramana o consecinta implicita a formei codului — mai jos analiza celor 3 variante realiste.
|
||||
|
||||
### 1.2 Cele 3 variante
|
||||
|
||||
**A. Parametrul castiga mereu cand e populat** (= design-ul existent, ca atare).
|
||||
- *Ce se strica*: daca, dintr-un motiv oarecare (bug VFP, sau — mai realist — decizia 35: la
|
||||
**regenerare**, daca logica de derivare a lui `cont_venit` ar rula necondiionat pe toate liniile
|
||||
documentului, inclusiv cele scrise initial prin "Cauta in lista de preturi..." cu `id_pol` real),
|
||||
o linie ajunge la Oracle cu **ambele** populate, `SCC`-ul politicii reale e inlocuit tacut cu
|
||||
contul derivat generic, `SCD` cu optiunea de firma (posibil diferita de `NOTE_CONTABILE.SCD` al
|
||||
notei reale), `ASCD`/`ASCC`/`EXPLICATIE`/`CU_TVA` cu variantele generice ale ramurii noi. **Fara
|
||||
nicio eroare** — factura se emite, dar cu alta contare decat cea configurata prin politica. Cel
|
||||
mai periculos tip de defect: tace si trece verificarea vizuala (suma corecta, cont plauzibil).
|
||||
|
||||
**B. Politica castiga mereu cand `id_pol` e populat** (indiferent daca rezolva sau nu).
|
||||
- Ar cere schimbarea conditiei de switch din `cont_venit IS NOT NULL` in ceva de forma
|
||||
`id_pol IS NULL AND cont_venit IS NOT NULL` — adica: daca exista `id_pol` pe linie, mergi pe
|
||||
ramura veche necondiionat, chiar daca acel `id_pol` nu rezolva (caz FACT-024 de azi).
|
||||
- *Ce se strica*: exact scenariul in care mecanismul nou ar fi cel mai util — o linie cu `id_pol`
|
||||
populat gresit/stale (nu neaparat imposibil, doar improbabil in fluxul normal, vezi 1.3) —
|
||||
ar continua sa primeasca FACT-024 desi VFP a trimis explicit un cont de rezerva. Varianta cea mai
|
||||
fragila: defineste "castigatorul" pe **prezenta** unui camp, nu pe **rezolvarea** lui.
|
||||
|
||||
**C. Parametrul e folosit doar cand politica nu rezolva** (fallback real, nu switch pe camp).
|
||||
- Cere ca `SELECT INTO ... FROM VCRM_POLITICI_PRET_ART` (1.1) sa ramana **necondiionat**, exact ca
|
||||
azi (deja rulat pe fiecare linie, cost zero suplimentar), dar `EXCEPTION WHEN NO_DATA_FOUND` sa
|
||||
verifice `cont_venit`: daca e populat, ramura noua; daca nu, `RAISE FACT-024` ca azi. Semantic
|
||||
corect — politica, cand exista si rezolva, nu e niciodata inlocuita tacut; parametrul e strict ce
|
||||
a fost gandit sa fie: o plasa pentru cazul in care politica lipseste.
|
||||
- *Cost real*: restructurare interna a functiei, nu doar un `IF` la inceput. Codul de dupa blocul
|
||||
`EXCEPTION` verifica azi `IF V_COMPUS = 1 THEN ... ELSE <deschide cursor_articol> END IF`
|
||||
(`:7305,7391`) — `V_COMPUS` ramane `NULL` daca s-a intrat pe `EXCEPTION`, deci "cade" oricum pe
|
||||
ramura `ELSE` care deschide `cursor_articol` (gaseste zero randuri, nu declanseaza nimic, dar nici
|
||||
ramura noua). Trebuie introdus un flag explicit (`V_ARE_POLITICA`/similar) propagat din interiorul
|
||||
`EXCEPTION` pana la punctul de decizie, marind suprafata modificata fata de varianta A (care
|
||||
atinge doar granita functiei, cu restul intact).
|
||||
|
||||
### 1.3 Ar putea sa apara vreodata, in fluxul normal, ambele populate?
|
||||
|
||||
Verificat direct in aceasta runda (nu presupus): `crsfactura` (cursorul VFP din care se scrie orice
|
||||
linie) are `id_pol N(20) Null` (`COMUN\programe\ofacturare_comun.prg:1775`) — camp nullable, deci la
|
||||
`APPEND BLANK` (mecanismul liniilor libere, confirmat de S4e ca acelasi gest se foloseste si pentru
|
||||
"Alege din nomenclator...") ramane `.NULL.` implicit, nu `0`. La scriere, `poArt.id_pol` (scatter
|
||||
din randul respectiv) ajunge in apelul RPC ca `Nvl(Alltrim(Str(poArt.id_pol)),[NULL])`
|
||||
(`ofacturare.vc2:14072`, identic la `:18107`) — literal SQL `NULL` cand campul e `.NULL.`. **O linie
|
||||
"din nomenclator" trimite deci `id_pol = NULL` la Oracle prin constructie, nu doar prin conventie de
|
||||
utilizare** — cursorul de cautare al nomenclatorului (`caut_articol`, `ocautare.prg`, deja confirmat
|
||||
de `coresp_cont_venchelt.md` sectiunea 9d) nu are coloana `id_pol` in output, deci nu exista niciun
|
||||
punct in care combosql-ul ar putea popula acest camp cu o valoare reala pentru o astfel de linie.
|
||||
|
||||
Ramane un singur scenariu real de ambiguitate: **regenerarea** (decizia 35). La regenerare, toate
|
||||
liniile documentului (cele scrise initial prin "Cauta in lista de preturi...", cu `id_pol` real, **si**
|
||||
cele adaugate prin "Alege din nomenclator...", fara `id_pol`) trec din nou prin acelasi drum de
|
||||
scriere. Daca logica VFP care calculeaza `cont_venit` (sectiunea 3) ar rula **necondiionat** pe toate
|
||||
liniile din grid, in loc sa fie conditionata explicit de "linia nu are `id_pol`", ar produce exact
|
||||
combinatia periculoasa din varianta A. **Asta nu e un risc Oracle — e un risc de implementare VFP**,
|
||||
dar exact tipul de risc pe care design-ul Oracle trebuie sa nu-l agraveze printr-un switch care il
|
||||
face invizibil.
|
||||
|
||||
### 1.4 Recomandare
|
||||
|
||||
**Niciuna dintre cele 3 variante pure, ci varianta A (deja proiectata, cea mai simpla) plus o garda
|
||||
explicita pe combinatia ambigua**, nu o rezolvare tacita in orice directie:
|
||||
|
||||
```sql
|
||||
IF detalii_articol.cont_venit IS NOT NULL THEN
|
||||
IF detalii_articol.id_pol IS NOT NULL THEN
|
||||
RAISE_APPLICATION_ERROR(-20000,
|
||||
'Articolul ' || detalii_articol.id_articol ||
|
||||
' are simultan politica de pret (' || detalii_articol.id_pol ||
|
||||
') si cont de venit calculat — conflict netratat (FACT-0xx)');
|
||||
END IF;
|
||||
<ramura noua, ca in design>
|
||||
ELSE
|
||||
<tot codul de azi, neschimbat>
|
||||
END IF;
|
||||
```
|
||||
|
||||
Motivare, in ordine:
|
||||
1. **Combinatia nu trebuie sa apara niciodata in fluxul normal** (1.3) — `id_pol` ramane `NULL` prin
|
||||
constructie pentru orice linie scrisa prin gestul "linie libera". Daca totusi apare, e un semnal
|
||||
ca ceva e stricat in populate-ul liniei (VFP a trimis ambele campuri, posibil din cauza logicii
|
||||
de regenerare descrisa la 1.3) — situatie de bug de investigat, nu de "rezolvat" silentios.
|
||||
2. **Variantele B si C rezolva combinatia in cate o directie fixa** — ambele ascund o eroare de date
|
||||
in loc sa o semnaleze: B ar putea reintroduce FACT-024 pe o linie unde VFP a oferit deja o
|
||||
solutie; A fara garda ar inlocui tacut o politica reala. O garda explicita e singurul comportament
|
||||
care nu presupune ca stie mai bine decat datele ce s-a intamplat.
|
||||
3. **Cost de implementare minim**: un singur `IF` suplimentar, la intrarea in ramura deja proiectata
|
||||
— nu restructureaza funcia (spre deosebire de varianta C), nu schimba conditia switch-ului
|
||||
principal (spre deosebire de B).
|
||||
4. **Compatibilitate/regresie zero pe apelantii de azi**: cand `cont_venit` e `NULL` (toti apelantii
|
||||
existenti, care nu cunosc inca acest parametru), executia intra direct pe `ELSE` — garda nu se
|
||||
evalueaza niciodata, comportamentul e identic bit-cu-bit cu azi. Cand `cont_venit` e populat si
|
||||
`id_pol` e `NULL` (cazul intentionat, "din nomenclator"), garda trece nevazuta, ramura noua
|
||||
ruleaza normal.
|
||||
|
||||
**Numele codului de eroare** (`FACT-0xx` in schita de mai sus) ramane de ales de Marius, distinct de
|
||||
`FACT-024` (care ramane, neschimbat, pentru cazul "niciuna din cele doua" — linie fara politica si
|
||||
fara cont).
|
||||
|
||||
---
|
||||
|
||||
## 2. Suprafata de schimbare pe Oracle
|
||||
|
||||
Deja proiectata si verificata adversarial in `canal_cont_venit_fara_politica.md` si
|
||||
`parametru_cont_contabilizeaza_articol.md`; rezumat + completarea cu decizia 36 si garda de la
|
||||
sectiunea 1.4:
|
||||
|
||||
1. **Coloana noua**: `VANZARI_DETALII_TEMP.CONT_VENIT VARCHAR2(4) NULL` (fara `NOT NULL`, fara
|
||||
`CHECK` — acelasi tipar ca `ACT_TEMP.SCC`). Optional, recomandat pentru trasabilitate:
|
||||
`VANZARI_DETALII.CONT_VENIT`, plus extinderea listei explicite de coloane din
|
||||
`scrie_in_vanzari` (`PACK_FACTURARE:13705-13757`, confirmat ca listeaza 24 coloane explicit, nu
|
||||
`SELECT *` — de extins cu o a 25-a daca se alege trasabilitatea).
|
||||
2. **Parametru nou pe `adauga_articol_factura`** (`ff_...:4989-5015`), la coada, dupa `V_LOT`:
|
||||
`V_CONT_VENIT IN VARCHAR2 DEFAULT NULL` — apelul VFP existent (pozitional, se opreste la
|
||||
`V_LOT`, confirmat la doua locuri, `ofacturare.vc2:14069-14091` si `:18089-18114`, doua clase
|
||||
distincte cu aceeasi metoda, nu o duplicare) ramane neschimbat, echivalent cu "trimite NULL".
|
||||
Plumbing identic cu `V_CONT`/`V_CONT2`, o coloana in plus in `INSERT INTO VANZARI_DETALII_TEMP`
|
||||
(`ff_...:5222-5282`).
|
||||
3. **`contabilizeaza_articol` insasi NU primeste parametru nou** — ramane `VANZARI_DETALII_TEMP%ROWTYPE`;
|
||||
coloana noua ajunge automat prin `SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP`
|
||||
(`:6063`), fara nicio schimbare la cei 3 apelanti interni (`scrie_factura2`,
|
||||
`scrie_factura_avize_retur`, `scrie_aviz_retur`).
|
||||
4. **Ramura noua in `contabilizeaza_articol`**, cu garda de la sectiunea 1.4:
|
||||
- `SCC := detalii_articol.cont_venit`.
|
||||
- `SCD` (decizia 36): `PACK_SESIUNE.getoptiunefirma('FACT_SCD_ARTFPRET')`, cu fallback hardcodat
|
||||
`'4111'` cand optiunea lipseste/e goala — optiune noua in tabelul `OPTIUNI`
|
||||
(`VARTYPE='CHARACTER'`, script de migrare idempotent, tiparul exact al `RF_CONT_INCASARE_*`,
|
||||
deja folosit in acelasi pachet, `optiune_firma_cont_debit.md` sectiunea 3.4). Ramurile de aviz
|
||||
raman `'418'`/`'461'`, neschimbate.
|
||||
- `ASCD`/`ASCC` din `GetAnaliticByGrupUtilizatori(pack_facturare.nid_util, V_SCD/V_SCC)` — acelasi
|
||||
fallback deja folosit necondiionat pe ramurile de aviz azi.
|
||||
- `EXPLICATIE` din `detalii_articol.explicatia` (parametru deja existent, `V_EXPLICATIE`).
|
||||
- `ID_VENCHELT`/`ID_SECTIE` din `pack_facturare.nid_venchelt`/`nid_sectie_stoc` (fallback de
|
||||
sesiune deja folosit ca prioritate azi).
|
||||
- `IN_VALUTA` din `pack_facturare.nin_valuta` (parametru obligatoriu, fara `DEFAULT`, mereu
|
||||
curent — nu o presupunere, sursa solida).
|
||||
- `CU_TVA`: hardcodat `1`, **cu efect colateral real si masurat, nu "inofensiv"** —
|
||||
`parametru_cont_contabilizeaza_articol.md` sectiunea 2b arata ca pe combinatia specifica "linie
|
||||
fallback cu TVA 0% + discount global pe factura + acea linie e prima/singura vazuta" valoarea
|
||||
forteaza `nproc_tva_max`/`nid_jtva_coloana` pentru toata linia de discount a facturii. Ramane
|
||||
"de confirmat cu Marius", cu argumentul mai tare decat in propunerea initiala.
|
||||
- Apeluri o singura data (nu in bucla): `scrie_nota(...)`, `descarca_gestiune(...)` (garda
|
||||
identica, `nscadere_stoc=1 AND id_gestiune<>-1000 AND in_stoc=1`), discount (garda identica).
|
||||
- Articole compuse (`V_COMPUS=1`) exclus structural — o linie fara `id_pol` nu poate avea
|
||||
`ID_POL_ART`, deci intrebarea "e compus" nu se poate pune pe aceasta ramura (confirmat pe
|
||||
schema view-ului, nu presupus).
|
||||
5. **Ordinea de deploy**: DB inainte de EXE. Parametrul nou are `DEFAULT NULL`, deci EXE-ul vechi
|
||||
ruland pe DB-ul nou functioneaza neschimbat (nu trimite parametrul). EXE-ul nou ruland pe DB-ul
|
||||
vechi (fara coloana/parametru) ar esua la primul apel cu al 27-lea argument — deci EXE-ul nou
|
||||
**nu** poate merge inaintea migrarii DB. Ordine standard, fara surpriza.
|
||||
6. **Regenerarea (decizia 35)**: `initializeaza_date_factura` reseteaza starea de sesiune
|
||||
(`DELETE FROM VANZARI_DETALII_TEMP`, `nid_act := 0`) la fiecare emitere — nimic din ramura noua
|
||||
citeste vreo stare presupunand "documentul e nou". Calea Oracle e identica la regenerare, **cu o
|
||||
exceptie separata, in alt pachet**: reemiterea cu acelasi `ID_FACT` ar da azi `ORA-00001` pe
|
||||
`PK_DOCUMENTE` in `PACK_CONTAFIN.SET_IDFACT` — problema deja proiectata separat
|
||||
(`idfact_refolosire_si_documente.md`), nu intersecteaza si nu invalideaza design-ul de aici, dar
|
||||
ramane o bucata de lucru distincta, necesara pentru ca regenerarea sa fie completa.
|
||||
7. **Gol neadresat inca**: ramura `ntip = 4` ("factura din avize", `:7520-7537`) apeleaza
|
||||
`scrie_fact_aviz_custodie` in loc de `descarca_gestiune`+discount — design-ul nu specifica ce se
|
||||
intampla pe fallback aici. In practica improbabil (o linie de aviz sursa fara politica n-ar fi
|
||||
ajuns la factura pe acest drum, `adauga_articol_factura:5096` cere deja `A.ID_POL = V_ID_POL` pe
|
||||
avizul sursa), dar de exclus explicit inainte de implementare, nu de presupus tacit.
|
||||
|
||||
---
|
||||
|
||||
## 3. Derivarea contului in VFP
|
||||
|
||||
### 3.1 Sursele, in ordine (decizia 27)
|
||||
|
||||
1. **Articol gestionabil** (are `id_gestiune` valid, `in_stoc=1`): `CORESP_CONT_VENCHELT.CONT_VENIT`,
|
||||
cautat pe **contul de gestiune al liniei** (`poArt.Cont`, camp deja gathered pe `poArticol` in
|
||||
ambele metode `do_scrie_articole`, confirmat la `ofacturare.vc2:12945,13835` si echivalentele in
|
||||
`frm_facturare_articole2`). Cheia de join e mereu `CONT` (nu `ID_GESTIUNE`, nu `TIP_DOC`) —
|
||||
confirmat pe 4+ consumatori Oracle reali ai tabelului (`pack_vin`, `pack_devize`,
|
||||
`gestiune_pack_gest_import`, un raport din 2026), niciodata pentru `CONT_VENIT` insa — aceasta
|
||||
propunere ar fi **primul consumator real** al coloanei, desi populata din 2023 (20 conturi de
|
||||
gestiune uzuale: 301-303/3021-3028, 331/332, 341, 345/346/348, 361, 371, 381). View-ul VFP-safe e
|
||||
`VCORESP_CONT_VENCHELT` (`STERS=0` deja filtrat in view).
|
||||
2. **Articol negestionabil**, sau gestionabil dar corespondenta nu rezolva (rand lipsa pentru acel
|
||||
`CONT`): `NOM_ARTICOLE.CONT`, **doar daca** primul caracter e `6` sau `7` (`verific_cont` nu
|
||||
restrange campul la o clasa, deci poate contine legitim orice cont valid, inclusiv 6xx/7xx pe un
|
||||
articol negestionabil).
|
||||
3. **Fallback final**: literal `'704'`. Fara reteta de cod deja scrisa in `pack_facturare` pentru
|
||||
aceasta valoare, dar cu un precedent independent de "704 = cont implicit client" in alt subsistem
|
||||
(`anaf_efactura.vc2:8376`, `cconte = 704`) — nu o inventie, dar cod nou.
|
||||
|
||||
### 3.2 Unde sta codul si cand ruleaza
|
||||
|
||||
Locul natural: chiar in `do_scrie_articole` (ambele clase, `frm_facturare_articole` si
|
||||
`frm_facturare_articole2`, confirmat ca 2 locuri distincte de atins, nu 1), imediat inainte de
|
||||
construirea textului RPC pentru fiecare linie, **conditionat explicit de `Empty(Nvl(poArt.id_pol,0))`**
|
||||
— nu la momentul adaugarii liniei in grid. Doua motive:
|
||||
- **Regenerarea (decizia 35)** re-parcurge toate liniile la fiecare scriere; derivarea la momentul
|
||||
scrierii (nu la adaugare) garanteaza ca valoarea reflecta starea curenta a corespondentelor/
|
||||
nomenclatorului, nu una inghetata la momentul in care linia a fost adaugata initial in grid.
|
||||
- **Conditionarea explicita pe `id_pol` gol** (nu pe "linia vine din nomenclator" ca marcaj separat)
|
||||
e chiar garda ceruta la sectiunea 1.3-1.4: o linie cu `id_pol` populat nu trebuie sa primeasca
|
||||
niciodata o valoare pe `cont_venit`, indiferent de sursa ei — un singur punct de decizie, in
|
||||
oglinda exacta cu conditia pe care Oracle o va verifica la randul lui (sectiunea 1.4).
|
||||
|
||||
### 3.3 Ce se intampla cand derivarea esueaza
|
||||
|
||||
- **Corespondenta nu are rand pentru acel `CONT`** (gestiune fara corespondenta configurata): cade pe
|
||||
pasul 2 (`NOM_ARTICOLE.CONT`), nu pe eroare.
|
||||
- **`NOM_ARTICOLE.CONT` nu incepe cu 6/7** (cont de gestiune sau alt tip pe un articol negestionabil,
|
||||
configurare atipica): cade pe pasul 3 (`704`), nu pe eroare.
|
||||
- **Niciodata `NULL`/gol trimis la Oracle pentru o linie "din nomenclator"** — ultimul pas e un
|
||||
literal, nu o interogare care poate esua. Aceasta e proprietatea care face garda de la 1.4
|
||||
inofensiva pe fluxul normal: `cont_venit` e *intotdeauna* populat cand `id_pol` e gol, deci
|
||||
ramura noua din Oracle ruleaza mereu cand ar trebui, iar FACT-024 (ramura veche) nu mai poate fi
|
||||
atinsa de o linie din nomenclator dupa implementare — dispare exact problema pe care povestea o
|
||||
rezolva.
|
||||
- **Nu e proiectata aici** (ramane de decis la implementare, in afara acestei povesti): daca vreo
|
||||
validare suplimentara ar trebui sa verifice ca valorile derivate (`CONT_VENIT` din corespondenta,
|
||||
`NOM_ARTICOLE.CONT`) sunt conturi valide in planul de conturi al anului curent — azi
|
||||
`verific_cont` exista ca mecanism (`oproceduri_comune.prg:2389-2415`) dar nu e cablat automat pe
|
||||
acest drum nou.
|
||||
|
||||
---
|
||||
|
||||
## 4. Fluxul in formularul unificat
|
||||
|
||||
1. Utilizatorul deschide un document deja emis la modificare (`frm_modific2024`/formularul unificat,
|
||||
in afara perimetrului #6/omodificari.vc2, care nu se atinge aici) si alege din meniu "Alege din
|
||||
nomenclator..." (a doua sursa, langa "Cauta in lista de preturi...", ambele reduse la acelasi
|
||||
gest UI conform S4b/S4e).
|
||||
2. Gestul UI: `Select crsfactura / APPEND BLANK` (tiparul deja in productie la
|
||||
`frm_facturare_articole2.But_nou1.do_adauga`, `ofacturare.vc2:17118-17122`), focus pe celula de
|
||||
cautare, `combosql` legat pe cursorul filtrat pe nomenclator (`caut_articol`/echivalent, fara
|
||||
coloana `id_pol` in output — confirmat, sectiunea 1.3).
|
||||
3. **`crsfactura.id_c` ramane `0`** (valoarea implicita a campului `N(20)` fara `Null`, la
|
||||
`APPEND BLANK`) — valoare pe care Oracle nu o produce niciodata pentru `id_c`. `do_sterge`
|
||||
(potriveste pe `id_c`) e no-op prin constructie pe aceasta linie, fara nicio modificare de cod —
|
||||
exact regula deja adoptata in #13, reconfirmata aici pentru sursa "nomenclator" (S4e o stabilise
|
||||
pentru sursa "lista de preturi pe comanda"; acelasi cursor de scriere `crsfactura`, acelasi
|
||||
mecanism, doar alt cursor de cautare in fata).
|
||||
4. **`crsfactura.id_pol` ramane `.NULL.`** (camp `N(20) Null`, `ofacturare_comun.prg:1775`), pentru
|
||||
ca sursa de cautare (nomenclator) nu are aceasta coloana in output — confirmat direct in aceasta
|
||||
runda (sectiunea 1.3), nu presupus.
|
||||
5. Utilizatorul completeaza cantitate/pret (campuri editabile inline pe grid, tipar deja existent).
|
||||
6. La `do_scrie_articole` (salvarea documentului), pentru fiecare linie cu `Empty(Nvl(poArt.id_pol,0))`,
|
||||
se ruleaza derivarea din sectiunea 3 si se populeaza al 27-lea argument pozitional al apelului RPC
|
||||
catre `adauga_articol_factura` cu valoarea calculata; pentru restul liniilor (cele cu `id_pol`
|
||||
populat, "din lista de preturi"), argumentul ramane `NULL` — comportament identic cu azi.
|
||||
7. Documentul se salveaza; pe Oracle, `contabilizeaza_articol` ruleaza ramura noua (sectiunea 1-2)
|
||||
pentru liniile fara politica, ramura veche neschimbata pentru restul.
|
||||
8. **Editarea** (regenerare, decizia 35): acelasi `do_scrie_articole`, aceeasi conditie pe `id_pol`,
|
||||
acelasi rezultat — nu exista o a doua cale de contare pentru liniile deja existente pe document.
|
||||
|
||||
---
|
||||
|
||||
## 5. Ce ramane in afara (partea auto)
|
||||
|
||||
- `tip = -12` (facturare auto, ROAAUTO) — livrat separat, dupa ce mecanismul de mai sus e stabil pe
|
||||
documentele ROAFACTURARE (ordinea deja decisa in plan).
|
||||
- "Alte servicii" din ROAAUTO ramane exceptia ei — ocoleste complet `contabilizeaza_articol`, nu
|
||||
foloseste si nu va fi migrata sa foloseasca acest mecanism ca parte a acestei povesti; daca se
|
||||
decide vreodata unificarea, e o poveste separata.
|
||||
- Gridul read-only din `frm_modific2024` (al #6) — S4g incepe dupa ce #6 se termina (decizia 30),
|
||||
nicio proiectare de aici nu presupune sau modifica acel perimetru.
|
||||
- Validarea de cantitate/stoc pentru o linie liberă la modificare (analogul sectiunii 6 din
|
||||
`s4e_lista_preturi_pe_sursa.md`, dar pentru nomenclator, nu lista de preturi) — nu re-proiectata
|
||||
aici, acelasi gol semnalat deja de S4e se aplica identic si pe sursa "nomenclator"; de rezolvat cu
|
||||
acelasi mecanism (varianta (b), reutilizare `do_verifica_articol` cu `poArticol` explicit).
|
||||
|
||||
---
|
||||
|
||||
## 6. Teste minime
|
||||
|
||||
Pe langa cele deja listate in `canal_cont_venit_fara_politica.md` sectiunea 6 si
|
||||
`nota_contabila_fara_politica.md`, specifice acestei povesti:
|
||||
|
||||
1. **Factura normala cu politica, parametru NULL** — verifica ACT identic cu azi (regresie de baza).
|
||||
2. **Aviz cu articol adaugat din nomenclator (fara politica)** — `SCD` ramane `'461'`/`'418'`
|
||||
(ramurile de aviz raman hardcodate independent de ramura noua/veche), nu `getoptiunefirma`.
|
||||
3. **`ntip = 46`** (daca exista pe fluxul de modificare vizat) — `scrie_nota` nu se cheama pe acea
|
||||
ramura; de confirmat ca ramura noua nu e atinsa deloc pentru acest tip.
|
||||
4. **Articol gestionabil din nomenclator, cu corespondenta configurata** — un singur rand `ACT`,
|
||||
`SCC` = `CORESP_CONT_VENCHELT.CONT_VENIT` pentru contul de gestiune al liniei, `SCD` = optiunea
|
||||
de firma (sau `4111` daca optiunea lipseste), linie TVA scrisa separat.
|
||||
5. **Articol negestionabil din nomenclator, fara corespondenta aplicabila** — `SCC` = `NOM_ARTICOLE.CONT`
|
||||
daca incepe cu 6/7, altfel `'704'`.
|
||||
6. **Articol negestionabil** (`id_gestiune=-1000`) cu `cont_venit` populat — `descarca_gestiune` NU
|
||||
ruleaza.
|
||||
7. **Discount pe o linie din nomenclator** — foloseste `V_ASCD`/`V_CU_TVA` calculate in ramura noua.
|
||||
8. **Document in valuta** (`nin_valuta=1`) cu linie din nomenclator — `IN_VALUTA=1` pe nota,
|
||||
`SUMA_VAL` completat.
|
||||
9. **Linie fara politica si fara cont derivat** (nu ar trebui sa se poata construi prin UI, dar de
|
||||
testat direct pe Oracle) — FACT-024 tot apare, regresie negativa: garda nu s-a slabit.
|
||||
10. **Linie cu ambele populate** (construita direct la nivel de apel Oracle, nu prin UI — testeaza
|
||||
garda de la sectiunea 1.4, nu fluxul normal) — noua eroare explicita apare, nu o rezolvare
|
||||
tacita in nicio directie.
|
||||
11. **Regenerare pe un document mixt** (o linie din lista de preturi cu `id_pol`, o linie din
|
||||
nomenclator fara `id_pol`) — dupa regenerare, prima linie tot cu `SCC` din politica, a doua tot
|
||||
cu `SCC` derivat; niciuna nu trece pe ramura celeilalte.
|
||||
12. **`id_c` si `do_sterge`** (mostenit din tiparul S4e, de re-verificat pentru sursa nomenclator):
|
||||
o linie din nomenclator adaugata la modificare, apoi stearsa inainte de salvare — no-op pe
|
||||
`crsarticole`, fara efect asupra vreunei linii reale a documentului.
|
||||
|
||||
---
|
||||
|
||||
## 7. Riscuri si de decis de Marius
|
||||
|
||||
1. **Garda explicita pe combinatia `id_pol` + `cont_venit` ambele populate** (sectiunea 1.4) — e o
|
||||
recomandare de proiectare a acestui raport, nu o decizie deja luata de Marius; codul exact al
|
||||
erorii si numarul `FACT-0xx` raman de ales.
|
||||
2. **`CU_TVA` hardcodat `1`** — are un efect colateral masurat (nu doar teoretic), prin
|
||||
`nproc_tva_max`, pe combinatia specifica linie-scutita + discount global de factura. De confirmat
|
||||
explicit, nu de presupus inofensiv.
|
||||
3. **Decizia 36 (`SCD` prin optiune de firma)** — numele exact al cheii (`FACT_SCD_ARTFPRET` propus),
|
||||
daca se adauga validare de cont la citire (niciun precedent existent nu valideaza), si
|
||||
`PROGRAME` (restrans la ROAFACTURARE sau extins ca `RF_CONT_INCASARE_*`) raman decizii deschise
|
||||
in `optiune_firma_cont_debit.md` sectiunea 4.
|
||||
4. **Ramura `ntip=4`/"factura din avize" pe fallback** (sectiunea 2 punctul 7) — probabil imposibil
|
||||
de atins prin acest drum (avizul sursa cere deja `id_pol`), dar nu exclus explicit prin cod sau
|
||||
test — de confirmat inainte de implementare.
|
||||
5. **Suprafata de regresie in restul suitei** (ROAGEST, ROAAUTO, ROAACNPRO, ROAIMOB) pentru
|
||||
`adauga_articol_factura` — pe baza cercetarilor existente, aceste produse folosesc proceduri
|
||||
separate (`_deviz`/`_stoc`) sau alt pachet complet (`pack_acn`), deci risc asteptat zero, dar
|
||||
cautarea directa in `COMUN`-urile lor pentru un apel simplu la `adauga_articol_factura(` nu s-a
|
||||
terminat in nicio runda anterioara — de re-rulat inainte de implementare, nu de presupus incheiata.
|
||||
6. **Validarea de cantitate/stoc pentru linia libera la modificare** (sectiunea 5) — gol mostenit
|
||||
de la S4e, nu inchis aici, de rezolvat cu acelasi mecanism pe ambele surse (lista de preturi si
|
||||
nomenclator).
|
||||
7. **Trasabilitatea `CONT_VENIT` pe `VANZARI_DETALII`** (sectiunea 2 punctul 1) — optionala pentru
|
||||
ca mecanismul sa functioneze, dar fara ea coloana nu se pastreaza dupa fapt; de decis daca merita
|
||||
extinderea listei explicite de coloane din `scrie_in_vanzari`.
|
||||
8. **`idfact_refolosire_si_documente.md`** — obstacol real pentru ca regenerarea (decizia 35) sa fie
|
||||
completa (reemitere cu acelasi `ID_FACT`), in `PACK_CONTAFIN`, nu in `pack_facturare` — nu
|
||||
blocheaza design-ul de aici, dar e o bucata de lucru separata, necesara inainte ca "editare =
|
||||
regenerare" sa functioneze end-to-end.
|
||||
|
||||
---
|
||||
|
||||
## STARE / CE RAMANE
|
||||
|
||||
Cercetare/proiectare incheiata pentru toate cele 6 puncte cerute in brief, cu punctul central
|
||||
(sectiunea 1) tratat explicit ca decizie de proiectare (nu doar consecinta implicita a codului
|
||||
existent deja schitat in rundele anterioare). Nicio editare de cod, niciun `git_sync.ps1`/
|
||||
`txt2vcx.ps1`, niciun commit, nicio scriere pe Oracle. Context consumat moderat in aceasta runda —
|
||||
nu a fost necesara predarea de mijloc de sesiune.
|
||||
|
||||
**Ce nu s-a putut inchide complet, de reluat separat**:
|
||||
- Cautarea exhaustiva a apelantilor `adauga_articol_factura` simplu in restul suitei (risc 5,
|
||||
sectiunea 7) — de re-rulat, nu s-a terminat in nicio runda anterioara din lipsa de timp, nu din
|
||||
cauza unei erori.
|
||||
- Confirmarea explicita a lui Marius pe hardcodarile/optiunile ramase deschise (`CU_TVA=1`, numele
|
||||
cheii de optiune, garda de la sectiunea 1.4) — sunt recomandari argumentate, nu decizii finale.
|
||||
502
docs/cercetare/s5_acoperire_tipuri.md
Normal file
502
docs/cercetare/s5_acoperire_tipuri.md
Normal file
@@ -0,0 +1,502 @@
|
||||
# Cercetare + proiectare — S5: acoperirea tuturor tipurilor de document
|
||||
|
||||
Proiectare pe cod, READ-ONLY (fara editari, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit, fara
|
||||
scriere pe Oracle — numai `SELECT`), pentru povestea **S5** din
|
||||
`docs\plan_13_unificare_formular_facturare.md` (`#### S5`). Nu s-a atins
|
||||
`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` (perimetrul altei
|
||||
sarcini in lucru) — doar citite cand au aparut in cautari (n-a fost cazul).
|
||||
|
||||
Sursa VFP: `COMUN\clase\ofacturare.vc2` (`frm_facturare_articole` = formularul de productie,
|
||||
`:10968-15739`; `frm_facturare_articole2` = prototipul, `:15741-19355` — **nu e subclasa** a
|
||||
primului, mostenesc separat din `_frmbase`, au `Init` propriu fiecare). Sursa de rutare:
|
||||
`COMUN\programe\ofacturare.prg` (`factureaza` = standard, `:81-...`; `factureaza2` = prototip,
|
||||
`:660-...`). Referinta de tipuri: `COMUN\docs\tipuri_documente_facturare.md`.
|
||||
|
||||
## Verdict (rezumat, citeste asta primul)
|
||||
|
||||
1. **`Do Case`-ul din `frm_facturare_articole.Init` (`ofacturare.vc2:15109-15248`) acopera 21 de
|
||||
valori de `tip`** (grupate in 14 ramuri): `1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,28,29,41,42,47`.
|
||||
2. **Patru tipuri sunt reale, reachable prin `factureaza()`, dar nu apar in niciun `Case`** —
|
||||
pierd titlu, cap de coloana, mesaj de stoc, vizibilitate discount, eliminare `cSerie`: **`45`
|
||||
(factura restaurant), `48`/`49` (custodie cu/fara descarcare K), `52` (contract, factura fiscala
|
||||
valuta)**. Confirmat pe meniuri (`Meniuri\politica.mn2:18,26,29`, `Meniuri\contracte.mn2:18`) si
|
||||
pe rutarea cursorului (`ofacturare.prg:271-282`), care **le recunoaste** — doar Init-ul
|
||||
formularului de articole nu le-a "prins" niciodata. **Nu doar `52`**, cum semnalase raportul S4b —
|
||||
sunt patru, nu unul.
|
||||
3. **Cinci tipuri din referinta (`43,44,46,50,51`) nu sunt niciodata pasate lui `factureaza()`
|
||||
in tot arborele `D:\ROA`** (cautare exhaustiva, zero potriviri) — nu ajung la acest formular deloc
|
||||
azi. `50` e marcat explicit "in lucru" in sursa; `51` (ROAACNPRO) foloseste `id_set=50100`, un
|
||||
interval separat de restul (`25000+`), semn ca provine dintr-un flux Oracle direct al altui produs,
|
||||
nu din `factureaza()` local.
|
||||
4. **Descoperire centrala, dincolo de ce cerea misiunea**: prototipul (`frm_facturare_articole2.Init`,
|
||||
`:18988-19080`) **nu e o versiune partiala a Do Case-ului standard — e aproape gol**. Singurul
|
||||
lucru pe care-l face pe tip e sa aleaga cuvantul `lcTipDoc` ("factura" vs "aviz"), pe o lista
|
||||
**mai scurta** (lipseste `24`). Nu seteaza titlu (nu exista `lb_titlu_alb_b121` in tot fisierul
|
||||
prototipului), nu schimba capul coloanei de cantitate, nu schimba mesajul de stoc, nu ascunde
|
||||
discountul, **nu are deloc conceptul de coloana `cSerie`** (gridul prototipului, `grd_factura`,
|
||||
n-are niciodata `RemoveObject('cSerie')` — cautare pe tot fisierul, zero potriviri in intervalul
|
||||
`15741-19355`). Daca formularul unificat porneste de la prototip (cum indica decizia de baza a
|
||||
planului), **toata diferentierea pe tip trebuie reconstruita de la zero**, nu doar completata.
|
||||
5. **Rutarea cursorului diverge intre standard si prototip pe trei tipuri, nu doua**: `23`
|
||||
(confirmat deja de S4/S4b), plus **`52` si `24`, gasite aici** — pe prototip, `Case Inlist(tnTip,
|
||||
2, 26, 6)` (`ofacturare.prg:762`) **omite `52`** fata de standard (`Inlist(tnTip, 2, 26, 6, 52)`,
|
||||
`:283`), si `Case Inlist(tnTip, 8, 9)` (`:819`) **omite `24`** fata de standard (`Inlist(tnTip, 8,
|
||||
9, 24)`, `:307`). Daca cineva ar factura tip `52` sau `24` prin prototip azi (`gnFacturareNou=1`),
|
||||
`lcSqlCursor` ar ramane nedefinit — eroare, nu doar diferenta de comportament.
|
||||
6. **Tipurile `26` si `52` n-au niciun bookkeeping `crsarticole`** (nici Rol A, nici Rol B) —
|
||||
inchis aici punctul lasat deschis de raportul S4 punctul 2: excluderea lor din toate cele patru
|
||||
`Case`-uri de bookkeeping din `do_adauga_articol`/`do_sterge` e totala (Do Case exhaustiv, fara
|
||||
ramura implicita), nu doar "neconfirmata".
|
||||
7. **`27` si `30` raman pe calea lor** — confirmat pe cod, cu o nuanta importanta pentru `30`: nu
|
||||
e un formular separat, ci **acelasi `frm_facturare_articole`, instantiat si trecut prin acelasi
|
||||
`Init`/`Do Case`, dar niciodata aratat** (`ofacturare.prg:444-453`: calculeaza totalurile, apasa
|
||||
programatic `but_termin1.Click()`, apoi `Release()`, fara `Show()`). Tip `30` **e afectat de
|
||||
golurile din `Do Case`** exact ca oricare alt tip needitat — doar ca defectele (titlu, cap de
|
||||
coloana) nu se vad niciodata pe ecran.
|
||||
|
||||
---
|
||||
|
||||
## 0. Metoda de verificare — lista de referinta
|
||||
|
||||
Lista completa de tipuri vine din `COMUN\docs\tipuri_documente_facturare.md` (sursa unica, deja
|
||||
verificata pe cod de acea cercetare). Tipuri incluse in tabelul de mai jos: toate cele din sectiunile
|
||||
"Facturi" si "Avize de expeditie" (documentele care intra prin `frm_facturare_articole`/`2`).
|
||||
Sectiunea "Tipuri negative" (`-1..-13`) **nu intra in acest formular** — sunt scrise de alte produse
|
||||
(ROAGEST, ROAAUTO) prin propriile lor fluxuri, niciodata prin `factureaza()` din ROAFACTURARE
|
||||
(cautare exhaustiva `factureaza(-` in tot `D:\ROA`, zero potriviri) — declarate aici explicit **ramase
|
||||
pe calea altui produs**, nu "neacoperite".
|
||||
|
||||
---
|
||||
|
||||
## 1. Tabelul complet, tip cu tip
|
||||
|
||||
Coloane: `tip` = `VANZARI.TIP` · **Case propriu** = are ramura proprie in `frm_facturare_articole.Init`
|
||||
(`ofacturare.vc2:15109-15248`)? · **titlu** = ce seteaza pe `lb_titlu_alb_b121.Caption` · **cap
|
||||
cantitate** = ce seteaza pe `grd_articole.cCantitate.header1.Caption` (implicit ramane cel din
|
||||
design, `[Cantitate in stoc]`, daca nu e suprascris) · **mesaj stoc** = `This.cmesaj_cantitate` ·
|
||||
**discount** = `clb_discount.Visible` · **`cSerie`** = coloana ramane (`Da`) sau se scoate (`Nu`) ·
|
||||
**butoane** = ce se face vizibil (`but_urmator_tot1`/`but_retur`, ambele `.F.` la design) · **cursor
|
||||
standard** = ramura din `factureaza` (`ofacturare.prg:266-308`) · **cursor prototip** = ramura din
|
||||
`factureaza2` (`:748-822`, gol daca lipseste) · **Rol crsarticole** = A (cantitate ramasa de
|
||||
facturat) / B (plafon de sesiune) / — (fara bookkeeping), din `docs\cercetare\s4_punct2_registru_cantitate_ramasa.md`
|
||||
· **stare S5** = acoperit azi / gol de completat / ramas pe calea veche / neatins.
|
||||
|
||||
### Facturi
|
||||
|
||||
| tip | denumire | Case propriu | titlu | cap cantitate | mesaj stoc | discount | `cSerie` | butoane vizibile | cursor standard | cursor prototip | Rol | stare S5 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 1 | lista de preturi (lei) | **Da** `:15122` (grup 1,5,7,10) | — (implicit) | "Cantitate in stoc" | "nu e pe stoc!" | vizibil (implicit) | **Nu** (scoasa) | `but_retur` | `cursor_preturi` (grup 1,22,5,29,7,10,23), `:279-282` | `cursor_preturi` (grup 1,22,5,29,7,10), `:758-761` | B (gestionabil, `1,22,29`) | acoperit azi, de portat |
|
||||
| 2 | contract (lei) | **Da** `:15129` (grup 2,6) | "FACTURA PE CTR. …" | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | — | `cursor_contract` (grup 2,26,6,52), `:283-291` | `cursor_contract` (grup 2,26,6 — **fara 52**), `:762-769` | B doar pt. `opt_facturare=0`; — pe rest | acoperit azi, de portat |
|
||||
| 3 | comanda | **Da** `:15144` | "FACTURA LA COMANDA …" | "Cantitate comandata" | "cantitate comandata facturata" | vizibil | **Nu** | `but_urmator_tot1` | `cursor_comanda` (grup 3,21,25,28,42,47), `:292-293` | `cursor_comanda` (acelasi grup), `:771-773` | **A** | acoperit azi, de portat |
|
||||
| 4 | din avize | **Da** `:15151` | "FACTURA DIN AVIZE" | — (implicit) | "cantitate de pe aviz facturata" | **ascuns** (`.F.`, `:15154`) | Da (nu se scoate) | `but_urmator_tot1` | `cursor_avize`, `:294-295` | `cursor_avize`, `:774-775` | **A** | acoperit azi, de portat |
|
||||
| 5 | lista de preturi valuta | **Da** `:15122` (grup 1,5,7,10) | — | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | `but_retur` | `cursor_preturi` | `cursor_preturi` | — | acoperit azi, de portat |
|
||||
| 6 | contract valuta | **Da** `:15129` (grup 2,6) | "FACTURA PE CTR. …" | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | — | `cursor_contract` | `cursor_contract` | B partial (ca 2) | acoperit azi, de portat |
|
||||
| 7 | credit note | **Da** `:15122` (grup 1,5,7,10) | — | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | `but_retur` | `cursor_preturi` | `cursor_preturi` | — | acoperit azi, de portat |
|
||||
| 8 | retur factura lei | **Da** `:15236` (grup 8,9) | — | "Cant. max. de returnat" | "nu se mai poate returna" | vizibil | Da (nu se scoate) | `but_urmator_tot1` | `cursor_retur`, `:306-307` | `cursor_retur` (grup 8,9), `:819-820` | B (invers) | acoperit azi, de portat |
|
||||
| 9 | retur factura valuta | **Da** `:15236` (grup 8,9) | — | "Cant. max. de returnat" | "nu se mai poate returna" | vizibil | Da | `but_urmator_tot1` | `cursor_retur` | `cursor_retur` | B (invers) | acoperit azi, de portat |
|
||||
| 10 | factura fiscala valuta | **Da** `:15122` (grup 1,5,7,10) | — | "Cantitate in stoc" | "nu e pe stoc!" | vizibil | **Nu** | `but_retur` | `cursor_preturi` | `cursor_preturi` | — | acoperit azi, de portat |
|
||||
| **43** | bon fiscal magazine (ROARETAIL) | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — niciodata pasat lui `factureaza()` (cautat in tot `D:\ROA`); colectat de la magazine prin alt flux |
|
||||
| **44** | factura hotel | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — la fel, zero apeluri `factureaza(44` |
|
||||
| **45** | factura restaurant | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** (implicit) | **nesetat** (ramane "nu e pe stoc!" default, `:15108`) | **nesetat** (ramane vizibil) | **Da, ramane** (nescoasa) | **nesetat** | `cursor_preturi`, `:275-278` | `cursor_preturi`, `:754-757` | — (exclus explicit din bookkeeping, `:12871,17178`) | **gol real de completat** — reachable din `Meniuri\politica.mn2:18` |
|
||||
| **46** | nota de plata restaurant | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — zero apeluri `factureaza(46`; zero documente in date de test (`tipuri_documente_facturare.md`, capcana 2) |
|
||||
| **48** | custodie cu descarcare K | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** | **nesetat** | **nesetat** | **Da, ramane** | **nesetat** | `cursor_articole_k`, `:271-273` | `cursor_articole_k`, `:750-752` | — | **gol real de completat** — reachable din `Meniuri\politica.mn2:29` (submeniu `Marfaincus`) |
|
||||
| **49** | custodie fara descarcare K | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** | **nesetat** | **nesetat** | **Da, ramane** | **nesetat** | `cursor_articole_k` | `cursor_articole_k` | — | **gol real de completat** — reachable din `Meniuri\politica.mn2:26` |
|
||||
| **50** | *(in lucru)* retur custodie | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins**, marcat explicit "in lucru" in `tipuri_documente_facturare.md` |
|
||||
| **51** | factura ROAACNPRO | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins** — `id_set=50100`, interval separat; probabil scris direct de ROAACNPRO, nu prin `factureaza()` local |
|
||||
| **52** | contract, factura fiscala valuta | **Nu** — lipseste din `Do Case` | **nesetat** | **nesetat** | **nesetat** | **nesetat** | **Da, ramane** | **nesetat** | `cursor_contract` (grup 2,26,6,52) | ***lipseste*** din grupul contract (`:762`) — `lcSqlCursor` nedefinit pe prototip | — (confirmat, vezi §2) | **gol real de completat** — reachable din `Meniuri\contracte.mn2:18` |
|
||||
|
||||
### Avize de expeditie
|
||||
|
||||
| tip | denumire | Case propriu | titlu | cap cantitate | mesaj stoc | discount | `cSerie` | butoane vizibile | cursor standard | cursor prototip | Rol | stare S5 |
|
||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||
| 21 | catre clienti, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — (implicit) | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
|
||||
| 22 | catre clienti, din lista | **Da** `:15178` (grup 22,29) | "AVIZ DE EXPEDITIE DIN LISTA DE PRETURI" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_preturi` | `cursor_preturi` | B | acoperit azi, de portat |
|
||||
| **23** | transfer subunitati, din lista | **Da** `:15187` | "TRANSFER INTRE SUBUNITATI" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | **`cursor_preturi`** (grup 1,22,5,29,7,10,**23**), `:279` | **`cursor_gestiune`** (grup **23**,41), `:776-778` | **B** (cod comun, indiferent de sursa) | acoperit azi, **dar sursa de cursor diverge intre forme — vezi §3** |
|
||||
| 24 | aviz retur | **Da** `:15242` | "RETUR AVIZ DE EXPEDITIE" | "Cant. max. de returnat" | "nu se mai poate returna" | (nemodificat aici) | **Nu** | `but_urmator_tot1` | `cursor_retur` (grup 8,9,**24**), `:306-307` | ***lipseste*** din grupul retur (`:819`, doar 8,9) — `lcSqlCursor` nedefinit pe prototip | B (invers) | acoperit azi, **dar prototipul n-are ramura de cursor — vezi §3** |
|
||||
| 25 | transfer subunitati, din comanda | **Da** `:15206` | "TRANSFER INTRE SUBUNITATI PE BAZA DE COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
|
||||
| 26 | catre clienti, din contract | **Da** `:15216` | "AVIZ DE EXPEDITIE DIN CONTRACTUL …" | "Cantitate in stoc" | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_contract` (grup 2,26,6,52) | `cursor_contract` (grup 2,26,6 — fara 52, dar 26 e prezent) | — (confirmat, §2) | acoperit azi, de portat |
|
||||
| **27** | transfer subunitati, pe lucrare | n/a — **ramane pe calea lui** | n/a | n/a | n/a | n/a | n/a | n/a | `cursor_lucrare`, `:302-304` | `cursor_lucrare`, `:815-817` | n/a | **ramas pe calea veche** — `frm_avizare_lucrare`, confirmat §4 |
|
||||
| 28 | catre clienti debitori, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
|
||||
| 29 | catre clienti debitori, din lista | **Da** `:15178` (grup 22,29) | "AVIZ DE EXPEDITIE DIN LISTA DE PRETURI" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_preturi` | `cursor_preturi` | B | acoperit azi, de portat |
|
||||
| **30** | transfer subunitati, pe NIR | n/a — **ramane pe calea lui, dar prin acelasi formular** | (irelevant — formular niciodata aratat) | (irelevant) | (irelevant) | (irelevant) | (irelevant) | (irelevant) | `cursor_aviz_nir`, `:299-300` | `cursor_aviz_nir`, `:813` | n/a | **ramas pe calea veche, cu nuanta** — vezi §4 |
|
||||
| 41 | retur transfer, lista pret | **Da** `:15197` | "RETUR TRANSFER" | — | "nu e pe stoc!" | **ascuns** | **Nu** | — | `cursor_gestiune`, `:296-298` | `cursor_gestiune` (grup 23,41) | **B** | acoperit azi, de portat |
|
||||
| 42 | catre clienti custodie, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
|
||||
| 47 | catre clienti custodie K, din comanda | **Da** `:15168` (grup 21,28,42,47) | "AVIZ DE EXPEDITIE DIN COMANDA" | — | "avizata cantitatea comandata" | **ascuns** | **Nu** | `but_urmator_tot1` | `cursor_comanda` | `cursor_comanda` | **A** | acoperit azi, de portat |
|
||||
| **50** | *(in lucru)* retur clienti custodie | Nu | — | — | — | — | — | — | *nicio ramura* | *nicio ramura* | n/a | **neatins**, "in lucru" |
|
||||
|
||||
**Tipuri negative** (`-1..-13`, ROAGEST/ROAAUTO): **ramase pe calea altui produs** — nu trec
|
||||
niciodata prin `factureaza()`/`factureaza2` din ROAFACTURARE (cautare exhaustiva, zero potriviri),
|
||||
deci nu intra in perimetrul Do Case-ului acestui formular. Declarate aici explicit, nu omise.
|
||||
|
||||
---
|
||||
|
||||
## 2. Tipurile care nu intra in niciun `Case` — inventar complet
|
||||
|
||||
Cerinta explicita a misiunii: "nu doar 52". Lista completa, verificata pe intreg `Do Case`-ul
|
||||
(`ofacturare.vc2:15109-15248`, citit integral, nu esantion) fata de lista de referinta:
|
||||
|
||||
**Nu apar in niciun `Case` al `frm_facturare_articole.Init`:**
|
||||
|
||||
| tip | reachable prin `factureaza()`? | ce pierde concret |
|
||||
|---|---|---|
|
||||
| 43 | Nu (0 apeluri in tot `D:\ROA`) | irelevant — nu ajunge la acest formular |
|
||||
| 44 | Nu | irelevant |
|
||||
| **45** | **Da** (`Meniuri\politica.mn2:18`) | titlu, cap coloana cantitate, mesaj de stoc, `cSerie` nescoasa (ramane vizibila, desi tip 45 e explicit exclus din bookkeeping-ul de cantitate — cele doua lucruri nu sunt legate) |
|
||||
| 46 | Nu | irelevant — zero documente si in datele de test |
|
||||
| **48** | **Da** (`Meniuri\politica.mn2:29`, submeniu `Marfaincus`) | idem 45 |
|
||||
| **49** | **Da** (`Meniuri\politica.mn2:26`) | idem 45 |
|
||||
| 50 | Nu, "in lucru" | irelevant azi |
|
||||
| 51 | Nu (interval `id_set` separat, alt produs) | irelevant pentru acest formular |
|
||||
| **52** | **Da** (`Meniuri\contracte.mn2:18`) | titlu (ramane cel implicit al formularului), `cSerie` nescoasa, cap coloana cantitate implicit — **cel mai vizibil defect, pentru ca 2/6 (acelasi grup logic) au titlu corect** |
|
||||
|
||||
**Concluzie**: din cele noua tipuri fara `Case`, **patru sunt reale si vizibile utilizatorului azi**
|
||||
(`45, 48, 49, 52`) — acestea sunt golul de completat cu valoare, nu doar `52`. Celelalte cinci
|
||||
(`43,44,46,50,51`) nu ajung niciodata la acest formular in fluxul curent — nu au nevoie de ramura in
|
||||
`Do Case` **pana cand** ceva le conecteaza la `factureaza()` (posibil, dar in afara perimetrului
|
||||
observabil aici; de tratat ca risc, nu ca bug, la sectiunea 10).
|
||||
|
||||
**De ce raman "invizibile" azi cu `cSerie`/titlu implicit, nu cu eroare**: `Do Case ... Endcase` fara
|
||||
ramura `Otherwise` in VFP nu genereaza nicio eroare cand nimic nu se potriveste — pur si simplu sare
|
||||
peste tot blocul. De-asta tip 45/48/49/52 "merg" (formularul se deschide, factureaza cu succes), doar
|
||||
cu UI-ul netratat pentru cazul lor specific — un defect tacut, nu un crash, motiv probabil pentru care
|
||||
n-a fost observat/raportat pana acum.
|
||||
|
||||
---
|
||||
|
||||
## 3. Divergentele standard vs. prototip
|
||||
|
||||
| Aspect | Standard (`factureaza`) | Prototip (`factureaza2`) | Comportament corect de pastrat |
|
||||
|---|---|---|---|
|
||||
| **Cursor pe tip 23** | `cursor_preturi` (grup `1,22,5,29,7,10,23`, `:279`) — tratat ca lista de preturi | `cursor_gestiune` (grup `23,41`, `:776-778`) — tratat ca transfer | **`cursor_gestiune`**, impreuna cu 41 (deja stabilit de S4/S4b: transferul e o singura familie de tip, indiferent daca porneste "din lista" sau "retur"; tratarea ca lista de preturi pe standard e inconsistenta cu propriul titlu "TRANSFER INTRE SUBUNITATI" pe care tot standardul il afiseaza pentru tip 23) |
|
||||
| **Cursor pe tip 52** | prezent, grupat cu `2,26,6` (`:283`) | **absent** din grupul contract (`:762`, doar `2,26,6`) — `lcSqlCursor` ramane nedefinit daca cineva factureaza tip 52 prin prototip | **prezent**, grupat cu `2,6,26` — lipsa lui pe prototip e o eroare de portare, nu o alegere deliberata (nimic in cod sugereaza ca 52 trebuia tratat diferit de 2/6/26 la nivel de cursor) |
|
||||
| **Cursor pe tip 24** | prezent, grupat cu `8,9` (`:306-307`) | **absent** din grupul retur (`:819`, doar `8,9`) — `lcSqlCursor` nedefinit | **prezent**, grupat cu `8,9` — acelasi tip de omisiune ca la 52 |
|
||||
| **Init: diferentiere pe tip** | 14 ramuri, seteaza titlu/cap coloana/mesaj/discount/`cSerie`/butoane | practic nimic — doar `lcTipDoc` ("factura"/"aviz"), pe o lista **fara tip 24** | **toata logica standardului**, portata — prototipul nu are nimic de pastrat aici in afara de pozitia `lcTipDoc` |
|
||||
| **Coloana `cSerie`** | exista in grid prin design, se scoate condiționat (10 din 21 tipuri acoperite) | **nu exista deloc** ca si coloana in `grd_factura` (gridul unic al prototipului) | de decis explicit la proiectare (§5) — nu e o simpla portare, gridul insusi trebuie sa capete coloana |
|
||||
| **`but_urmator_tot1` (sau echivalentul lui)** | vizibil pe 9 din 21 de tipuri (§1) | nu exista conceptul in Init — prototipul nu are nimic care sa corespunda azi | de portat lista completa de vizibilitate din standard |
|
||||
| **`clb_discount.Visible`** | ascuns explicit pe toate tipurile de aviz (`21,28,42,47,22,29,23,41,25,26`) | niciodata atins in Init | de portat integral |
|
||||
|
||||
**De ce conteaza asta pentru S5**: planul spune ca formularul unificat se bazeaza pe prototip
|
||||
(arhitectura lui: grid unic, editare inline, cautare pe server — deja deciziile S1-S4). Dar
|
||||
**diferentierea pe tip nu vine "aproape gata" din prototip** — vine aproape in intregime din
|
||||
standard, si trebuie portata, nu doar completata cu cele patru tipuri lipsa. Cele doua liste (tipuri
|
||||
lipsa din standard: 45/48/49/52; tot ce lipseste din prototip: aproape totul) sunt probleme
|
||||
**diferite**, care se rezolva **in aceeasi miscare** daca proiectarea de la §5 porneste de la o
|
||||
sursa unica de configurare portata integral din standard, cu cele patru completari incluse de la
|
||||
inceput (nu adaugate separat, dupa portare).
|
||||
|
||||
---
|
||||
|
||||
## 4. Tipurile speciale (27, 30) — confirmate pe cod
|
||||
|
||||
### Tip 27 — transfer pe baza de lucrare
|
||||
|
||||
Confirmat la trei niveluri, toate in `ofacturare.prg`:
|
||||
- `Do Case tnTip = 27 -> poDate.nIdTipDoc = 6` (`:188-189`, tip document AVIZ);
|
||||
- `Do Case tnTip = 27 -> lcObiect = [frm_date_aviz_lucrare]` (`:218-219`) — **formular de antet
|
||||
diferit**, nu `frm_date_aviz`/`frm_date_factura`;
|
||||
- `Do Case tnTip = 27 -> lcObject = [frm_avizare_lucrare]` (`:386-387`) — **formular de articole
|
||||
diferit**, nu `frm_facturare_articole`. Cursorul sursa e si el propriu: `cursor_lucrare`
|
||||
(`:302-304`), populat pe `poDate.id_lucrare`, un camp pe care restul tipurilor nu-l au.
|
||||
|
||||
**Ce il tine pe calea lui**: `id_lucrare` — o legatura pe care niciun alt tip de document n-o are
|
||||
(lucrare de service/executie, nu comanda/aviz/contract). `frm_avizare_lucrare` grupeaza gestiunile
|
||||
destinatie diferit (`crsgestiunidest`, `:388-393`, cu optiunea `<TOATE>`), o structura pe care
|
||||
`frm_facturare_articole`/`2` n-o au. **Formularul unificat n-ar avea `id_lucrare` si n-ar avea
|
||||
gruparea pe gestiuni destinatie** — motiv suficient sa ramana separat, confirmat pe cod, nu
|
||||
presupus.
|
||||
|
||||
### Tip 30 — transfer pe baza de NIR
|
||||
|
||||
**Nuanta importanta, gasita aici**: tip 30 **nu ocoleste** `frm_facturare_articole` — il
|
||||
instantiaza, exact ca orice alt tip din grupul "otherwise" (`ofacturare.prg:395`, `lcObject =
|
||||
[frm_facturare_articole]`, ramura `Else` a lui `If tnTip = 27`). Trece prin acelasi `Init`, acelasi
|
||||
`Do Case` de la `:15109-15248` (unde `30` nu are ramura proprie — ar avea aceleasi goluri ca 45/48/49
|
||||
daca ar fi vreodata aratat). Diferenta reala: **formularul nu e niciodata aratat**
|
||||
(`ofacturare.prg:444-453`):
|
||||
|
||||
```
|
||||
IF tnTip = 30 && AVIZ DIN NIR
|
||||
ofrmdetaliifactura.do_calculeaza_totaluri()
|
||||
ofrmdetaliifactura.but_termin1.Click()
|
||||
plVizibil = .F.
|
||||
...
|
||||
ELSE
|
||||
...
|
||||
If plVizibil
|
||||
ofrmdetaliifactura.Show()
|
||||
Else
|
||||
ofrmdetaliifactura.Release()
|
||||
pnButon = 2
|
||||
Endif
|
||||
ENDIF
|
||||
```
|
||||
|
||||
**Ce il tine pe calea lui**: nu structura formularului (e acelasi obiect), ci **automatizarea
|
||||
completa a fluxului** — cursorul sursa (`cursor_aviz_nir`, populat din `VRUL`/tranzactii de receptie,
|
||||
nu din comanda/lista de preturi) vine deja complet, iar codul apeleaza direct metodele de finalizare
|
||||
fara interactiune. **Pentru formularul unificat**: daca arhitectura noua pastreaza acelasi tipar
|
||||
("creeaza obiectul, populeaza, cheama finalizarea, `Release()` fara `Show()`"), tip 30 continua sa
|
||||
functioneze neschimbat — nu are nevoie de ramura in configurarea vizuala (§5), pentru ca vizualul nu
|
||||
se vede niciodata. **Singurul risc real**: daca `do_calculeaza_totaluri()`/`but_termin1.Click()` ale
|
||||
formularului unificat ajung sa citeasca vreo proprietate pe care doar `Do Case`-ul vizual o seteaza
|
||||
azi (de exemplu, un cod care ar verifica `This.cmesaj_cantitate` sau `lcTipDoc` in logica de calcul,
|
||||
nu doar in UI) — **de verificat explicit la implementare**, nu presupus ca "nu conteaza pentru ca nu
|
||||
se vede".
|
||||
|
||||
---
|
||||
|
||||
## 5. Ce structura inlocuieste `Do Case`-ul de 140 de linii
|
||||
|
||||
### Optiunile comparate
|
||||
|
||||
**(a) Pastreaza `Do Case`, doar completeaza-l** (adauga ramuri pentru 45/48/49/52, porteaza restul in
|
||||
prototip). Cost minim imediat, dar **nu rezolva problema de fond**: un `Do Case` fara `Otherwise` nu
|
||||
semnaleaza niciodata un tip lipsa — exact mecanismul care a produs golul de azi (patru tipuri reale
|
||||
pierdute, ani la rand, fara nicio eroare). Orice tip nou de document adaugat in viitor (suita are deja
|
||||
`46,50` "in lucru", `43,44,51` din alte fluxuri) risca aceeasi soarta.
|
||||
|
||||
**(b) Metoda separata per grup de tipuri** (`configureaza_lista_preturi()`, `configureaza_comanda()`,
|
||||
...). Mai clar decat un `Do Case` unic, dar tot **implicit** — un tip nou tot nu declanseaza nicio
|
||||
eroare daca nimeni nu-l adauga in metoda corecta; doar muta problema din 140 de linii intr-un fisier
|
||||
cu mai multe metode mici, fara sa adauge un mecanism de detectie.
|
||||
|
||||
**(c) Tabel de configurare per tip (RECOMANDAT)**. Un cursor/tabel cu **un rand per `tip`**, coloanele
|
||||
fiind exact proprietatile pe care `Do Case`-ul le seteaza azi: `titlu`, `cap_cantitate`, `mesaj_stoc`,
|
||||
`discount_vizibil` (`L`), `are_serie` (`L`), `tip_doc` (`factura`/`aviz`), `buton_tot_vizibil` (`L`),
|
||||
`buton_retur_vizibil` (`L`), `grup_sursa` (pentru meniul S4b: `lista/comanda/aviz-comanda/transfer/
|
||||
retur/contract-articole/contract-rate`). Populat printr-un singur bloc de `INSERT INTO` (sau un DBF
|
||||
static, `configuratie_tip_document.dbf`, editabil fara compilare) — **un rand per tip din
|
||||
`tipuri_documente_facturare.md`**, inclusiv cele patru azi lipsa.
|
||||
|
||||
`Init` devine:
|
||||
```
|
||||
SELECT * FROM configuratie_tip_document WHERE tip = poDate.tip INTO CURSOR crscfgtip
|
||||
If Reccount('crscfgtip') = 0
|
||||
* tip necunoscut -- eroare explicita, nu formular netratat tacut
|
||||
AMESSAGEBOX("Tip de document necunoscut in configurare: " + Transform(poDate.tip), 16, "Eroare configurare")
|
||||
Thisform.Release()
|
||||
Return
|
||||
Endif
|
||||
This.lb_titlu_alb_b121.Caption = crscfgtip.titlu
|
||||
This.grd_articole.cCantitate.header1.Caption = crscfgtip.cap_cantitate
|
||||
This.cmesaj_cantitate = crscfgtip.mesaj_stoc
|
||||
This.clb_discount.Visible = crscfgtip.discount_vizibil
|
||||
If !crscfgtip.are_serie
|
||||
This.grd_articole.RemoveObject('cSerie')
|
||||
Endif
|
||||
This.but_urmator_tot1.Visible = crscfgtip.buton_tot_vizibil
|
||||
This.but_retur.Visible = crscfgtip.buton_retur_vizibil
|
||||
```
|
||||
|
||||
**De ce e mai bun decat (a)/(b) pe cost de intretinere**:
|
||||
- **Un tip lipsa devine o eroare vizibila la deschidere**, nu un formular netratat tacut — exact
|
||||
defectul care a permis golul de azi sa treaca neobservat ani la rand.
|
||||
- **"Cat de usor se vede un tip lipsa" e mecanic, nu vizual** — vezi §8, o interogare simpla compara
|
||||
lista de tipuri din configurare cu lista de referinta din `tipuri_documente_facturare.md`, fara sa
|
||||
ruleze formularul.
|
||||
- **Grupurile identice raman explicite, nu implicite** — azi, "tipurile 21,28,42,47 au acelasi titlu"
|
||||
se vede doar citind `Inlist(...)`; intr-un tabel, acelasi lucru se vede ca patru randuri cu aceeasi
|
||||
valoare in coloana `titlu` — usor de generat cu un singur `INSERT` per grup (`FOR EACH tip IN
|
||||
(21,28,42,47) ... INSERT ...`), nu mai putin explicit, dar auditabil cu `SELECT titlu, COUNT(*)
|
||||
GROUP BY titlu`.
|
||||
- **Coloana `are_serie` rezolva si divergenta standard/prototip de la §3** — prototipul nu are azi
|
||||
conceptul deloc; cu configurarea noua, adaugarea coloanei `cSerie` in gridul unificat devine
|
||||
conditionata de aceeasi sursa unica, indiferent de forma de baza.
|
||||
|
||||
**Cost**: portarea initiala a ~21 de randuri (14 ramuri distincte de azi + 4 completari + grupare
|
||||
explicita), plus un nou tabel/cursor de intretinut. Nu e cod nou complex — e date, nu logica; riscul
|
||||
de regresie e in acuratetea portarii (fiecare valoare trebuie sa corespunda exact cu ce face azi
|
||||
`Do Case`-ul), verificabil linie cu linie fata de tabelul din §1.
|
||||
|
||||
**Recomandare finala**: **(c)**, cu tabelul de configurare implementat ca `DBF` static (nu cursor
|
||||
generat in cod) — editabil de oricine fara sa recompileze, si direct verificabil cu `SELECT` fara sa
|
||||
porneasca formularul (vezi §8).
|
||||
|
||||
---
|
||||
|
||||
## 6. Grupuri naturale de tipuri
|
||||
|
||||
Din tabelul §1, grupurile care au azi (sau ar trebui sa aiba) valori identice pe toate coloanele:
|
||||
|
||||
| Grup | Tipuri | Ce difera **in interiorul** grupului |
|
||||
|---|---|---|
|
||||
| **Lista de preturi, fara stoc special** | 1, 5, 7, 10 | Nimic in Init — difera doar valuta/tip document la nivel de antet (`poDate.in_valuta`, `nIdTipDoc`), nu in acest formular |
|
||||
| **Contract, factura** | 2, 6 (+ **52** de adaugat) | Nimic in Init dupa completare — `52` e valuta, ca `6`, dar cu alt `id_set`; titlul/coloanele sunt identice |
|
||||
| **Aviz din comanda (clienti/debitori/custodie)** | 21, 28, 42, 47 | Nimic in Init — difera doar destinatia comerciala (client normal/debitor/custodie), invizibila la acest nivel |
|
||||
| **Aviz din lista de preturi** | 22, 29 | Nimic — difera doar client normal/debitor |
|
||||
| **Retur facturi** | 8, 9 | Nimic — lei/valuta |
|
||||
| **Transfer subunitati** | 23, 41 | Sens (din lista vs. retur) — titlu diferit ("TRANSFER..." vs "RETUR TRANSFER"), restul identic; **trebuie unificate pe cursor** (§3) inainte de unificare vizuala |
|
||||
| **Comanda proprie** | 3 | Singur — cap de coloana propriu ("Cantitate comandata") |
|
||||
| **Avize proprii** | 4 | Singur — discount vizibil (spre deosebire de toate celelalte avize) |
|
||||
| **Aviz din contract** | 26 | Singur — titlu propriu, dar cursor comun cu grupul contract |
|
||||
| **Aviz retur** | 24 | Singur — cursor comun cu 8/9, dar titlu si `but_urmator_tot1` proprii |
|
||||
| **Custodie K** | 48, 49 | Identice ca structura vizuala (ambele lipsesc azi) — difera doar `cu`/`fara` descarcare K, invizibil la acest nivel |
|
||||
| **Restaurant** | 45 | Singur, azi lipsa |
|
||||
|
||||
Observatie de proiectare: grupurile "aviz din comanda" (21/28/42/47) si "comanda" (3) au **acelasi**
|
||||
cap de coloana si mesaj de stoc conceptual ("cantitate comandata"), dar text usor diferit
|
||||
("facturata"/"avizata") — pastrate distincte in tabelul de configurare (nu fortate identice), pentru
|
||||
ca diferenta e deliberata in codul de azi (`:15149` vs `:15176`, verb diferit).
|
||||
|
||||
---
|
||||
|
||||
## 7. Pasi de implementare, ordonati
|
||||
|
||||
1. **Extrage tabelul de configurare din `Do Case`-ul standard, exhaustiv** — un rand per tip din
|
||||
`tipuri_documente_facturare.md` care intra prin acest formular (Facturi + Avize, exclus 27/30/
|
||||
negative), valorile copiate exact din §1. *Gata cand*: `SELECT DISTINCT tip FROM
|
||||
configuratie_tip_document` produce exact multimea `{1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,28,29,
|
||||
41,42,45,47,48,49,52}` (23 de tipuri) — nici unul in minus, nici unul in plus fata de lista
|
||||
calculata in §8.
|
||||
2. **Completeaza cele patru randuri azi lipsa (45,48,49,52)** cu valori coerente cu grupul lor logic
|
||||
(45 langa lista de preturi cu stoc dezactivat conceptual; 48/49 langa custodie K; 52 langa 2/6) —
|
||||
decizie de continut, nu doar de structura; **de validat cu Marius inainte de a le considera
|
||||
"gata"**, pentru ca azi nu exista niciun titlu/mesaj de referinta pentru ele (nimeni nu l-a vazut
|
||||
pe ecran). *Gata cand*: cele patru randuri au valori nenule pe toate coloanele obligatorii
|
||||
(`titlu`, `cap_cantitate`, `mesaj_stoc`).
|
||||
3. **Corecteaza rutarea cursorului**: `23` trece pe `cursor_gestiune` (unificat cu `41`, nu mai divide
|
||||
standard/prototip); `52` si `24` primesc ramura de cursor pe orice cale ramane vie din prototip
|
||||
(daca formularul unificat pastreaza `factureaza2`-stil, sau devine parte din calea unica daca
|
||||
`factureaza`/`factureaza2` se unesc — decizie separata, in afara acestei povesti). *Gata cand*:
|
||||
deschiderea formularului pe tip `23`, `24` si `52` prin calea noua produce acelasi continut in
|
||||
`crsarticole` ca varianta care functiona deja (`23`→prototip vechi pentru comparatie de continut,
|
||||
`24`/`52`→standard).
|
||||
4. **Adauga coloana `cSerie` in gridul unificat, condiționata pe `are_serie`** — azi absenta din
|
||||
gridul prototipului; adaugata o singura data, aratata/ascunsa din configurare, nu prin
|
||||
`RemoveObject` scris de mana pe fiecare tip. *Gata cand*: pe un tip cu `are_serie=.T.` (ex. 4)
|
||||
coloana e vizibila; pe un tip cu `are_serie=.F.` (ex. 3) nu e.
|
||||
5. **Inlocuieste `Do Case`-ul din `Init` cu citirea din configurare** (structura din §5), inclusiv
|
||||
ramura de eroare explicita pe tip necunoscut. *Gata cand*: pentru fiecare din cele 23 de tipuri,
|
||||
deschiderea formularului seteaza exact valorile din tabelul §1/pasul 2 (comparatie automata, nu
|
||||
vizuala — proprietatile sunt citibile headless).
|
||||
6. **Verifica tipurile speciale raman neatinse**: 27 (cale total separata, neschimbata), 30 (acelasi
|
||||
formular, dar `do_calculeaza_totaluri`/`but_termin1.Click()` nu citesc nimic setat doar de vechiul
|
||||
`Do Case` vizual — verificare explicita, §4). *Gata cand*: un document tip 30 de test se
|
||||
finalizeaza cu acelasi rezultat in `VANZARI`/`VANZARI_DETALII` inainte si dupa migrare.
|
||||
7. **Documenteaza tipurile neatinse (43,44,46,50,51) ca decizie explicita**, nu ca omisiune — un
|
||||
comentariu in tabelul de configurare (`* 43,44,46,50,51: neconectate la factureaza() in
|
||||
ROAFACTURARE, verificat <data>`) ca viitorii cititori sa nu presupuna ca lipsesc din greseala.
|
||||
*Gata cand*: comentariul exista si linkeaza spre acest raport.
|
||||
|
||||
*Depinde de*: S4 (cautarea articolelor pe server) pentru arhitectura gridului unic — pasul 4 de aici
|
||||
presupune ca gridul unificat exista deja in forma stabilita de S4; S4b (bara de butoane) pentru
|
||||
`buton_tot_vizibil`/`buton_retur_vizibil`, care alimenteaza si meniul `xmenu()` de acolo — coloanele
|
||||
`grup_sursa` din tabelul de configurare (§5) sunt exact ce cere S4b sectiunea 3.
|
||||
|
||||
---
|
||||
|
||||
## 8. Cum se verifica ca acoperirea e completa — proba mecanica
|
||||
|
||||
**Nu o citire — o interogare care compara doua liste.** Doua surse de adevar:
|
||||
|
||||
1. **Lista de referinta**: tipurile din `COMUN\docs\tipuri_documente_facturare.md`, sectiunile
|
||||
"Facturi" si "Avize de expeditie", **minus** cele confirmate neatinse azi de acest formular (27, 30
|
||||
raman — vezi nuanta §4 — dar 43,44,46,50,51 se exclud daca raman neconectate; de recalculat lista
|
||||
la fiecare rulare, nu de la o constanta inghetata).
|
||||
2. **Lista din configurare** (dupa implementarea §5): `SELECT DISTINCT tip FROM
|
||||
configuratie_tip_document`.
|
||||
|
||||
**Script de verificare** (headless, fara UI, rulabil oricand):
|
||||
|
||||
```foxpro
|
||||
* verifica_acoperire_tip.prg — proba mecanica pentru S5
|
||||
LOCAL lnLipsa, lnInPlus
|
||||
* 1. lista de referinta -- tinuta manual sincron cu tipuri_documente_facturare.md
|
||||
* (facturi + avize, exclus negative; 27/30 raman in lista, tratate separat la pasul 3)
|
||||
DIMENSION laReferinta[23]
|
||||
laReferinta = [1,2,3,4,5,6,7,8,9,10,21,22,23,24,25,26,27,28,29,30,41,42,47,52] && + 45,48,49 dupa Pasul 2
|
||||
|
||||
* 2. lista din configurare
|
||||
SELECT DISTINCT tip FROM configuratie_tip_document INTO CURSOR crsCfg
|
||||
|
||||
* 3. tipuri de referinta fara configurare (exclus 27, 30 -- cale separata confirmata)
|
||||
* 4. tipuri in configurare fara corespondent in referinta (config "orfana")
|
||||
* -- ambele liste trebuie sa fie goale la "gata"
|
||||
```
|
||||
|
||||
Alternativ, **fara sa astepte implementarea §5**: acelasi principiu se aplica azi direct pe
|
||||
`Do Case`-ul din `ofacturare.vc2:15109-15248`, extragand tipurile din fiecare `Case ... Inlist/=` prin
|
||||
`vfp_symbols.ps1 -Grep 'Case (poDate\.tip|Inlist\(poDate\.tip'` si comparand rezultatul cu lista de
|
||||
referinta — exact tehnica folosita in aceasta cercetare pentru a produce tabelul din §1 (nu o
|
||||
citire vizuala, o extractie sistematica).
|
||||
|
||||
**Pentru cursor (rutare, §3)**: acelasi principiu, pe `ofacturare.prg`, comparand tipurile din fiecare
|
||||
`Do Case`/`Inlist` de la `:266-308` (standard) cu cele de la `:748-822` (prototip) — orice tip prezent
|
||||
intr-una si absent in cealalta e o divergenta de raportat (asa au fost gasite `52` si `24` in aceasta
|
||||
sesiune).
|
||||
|
||||
---
|
||||
|
||||
## 9. Ce nu se poate testa headless
|
||||
|
||||
- **Titlul, capul de coloana, mesajul de stoc ca text afisat efectiv pe ecran** — verificabile
|
||||
headless doar ca *proprietati setate* (`This.lb_titlu_alb_b121.Caption` dupa `Init()`, apelat direct
|
||||
cu un `poDate` de test), nu ca randare vizuala. Diferenta conteaza: o proprietate corect setata pe
|
||||
un control care nu exista (`lb_titlu_alb_b121` lipsa din prototip azi) ar trece "headless" fara sa
|
||||
arate nimic real — de confirmat mai intai ca ambele controale exista in formularul unificat.
|
||||
- **Coloanele de grid (`grd_articole`/`grd_factura`, coloana `cSerie` noua)** — capcana deja cunoscuta
|
||||
pe acest proiect: sub `-A -T` (rulare headless), `ColumnCount=0`/`RecordSource` raman artefacte,
|
||||
coloanele nu se materializeaza. Exista un harness UI **vizibil** care le citeste corect — orice
|
||||
verificare a `are_serie`/coloanelor trebuie sa treaca prin acela, nu prin `-A -T`.
|
||||
- **Tip 30 — fluxul complet fara `Show()`** — se poate rula headless (nu deschide nicio fereastra prin
|
||||
constructie), dar verificarea "rezultatul in Oracle e identic inainte/dupa" cere date de test reale
|
||||
(un NIR cu articole), nu doar apelul metodei.
|
||||
- **Popup-ul/dialogul viitor din S4b (`xmenu()`, `cauta_alfa`)**, daca `grup_sursa` din tabelul de
|
||||
configurare (§5/§6) ajunge sa-l alimenteze direct — mostenit ca limitare de la raportul S4b (`§8`
|
||||
de acolo): continutul `lcMeniu` verificabil ca text, popup-ul afisat nu.
|
||||
- **Comportamentul real al meniurilor `politica.mn2`/`contracte.mn2`** pentru tipurile 45/48/49/52 —
|
||||
se poate confirma ca exista intrarea de meniu (citire `.mn2`, facuta in aceasta cercetare), dar nu
|
||||
ca apasarea ei in productie deschide exact formularul asteptat, fara o rulare UI vizibila.
|
||||
|
||||
---
|
||||
|
||||
## 10. Riscuri si ce ramane de decis de Marius
|
||||
|
||||
1. **Continutul exact (titlu/mesaj) pentru cele patru tipuri azi netratate (45,48,49,52)** — codul nu
|
||||
ofera niciun precedent vizual (n-au fost vazute niciodata corect pe ecran), deci textele propuse in
|
||||
Pasul 2 (§7) sunt o **propunere**, nu o recuperare a unui text existent. **Recomandare**: 45 langa
|
||||
grupul "lista de preturi fara stoc" (e exclus explicit din bookkeeping de stoc, `:12871`); 48/49 cu
|
||||
titlu care mentioneaza "custodie" (singurul lucru care le distinge conceptual azi, in afara de
|
||||
coeficientul K, invizibil la acest nivel); 52 **identic cu 2/6** ("FACTURA PE CTR. ..."), pentru
|
||||
coerenta cu gruparea deja facuta corect in `frm_date_factura.Init` (`:9530,9632,9670`, grupul
|
||||
`2,6,52`).
|
||||
2. **Daca 23 trece pe `cursor_gestiune` pe calea unificata, comportamentul vizibil pentru operator se
|
||||
schimba** (sursa de articole devine stocul din gestiune, nu lista de preturi) — **decizie de produs,
|
||||
nu doar tehnica**, deja semnalata de S4/S4b, dar repetata aici pentru ca afecteaza direct §5/§7
|
||||
(tabelul de configurare trebuie sa reflecte alegerea finala, nu ambele variante). **Recomandare**:
|
||||
`cursor_gestiune`, argumentat de coerenta cu titlul "TRANSFER INTRE SUBUNITATI" pe care insusi
|
||||
standardul il afiseaza azi pentru 23 (titlul spune "transfer", cursorul azi spune "lista de
|
||||
preturi" — inconsistenta interna de rezolvat, nu de pastrat).
|
||||
3. **Daca 43,44,46,50,51 raman permanent neconectate la `factureaza()`, sau exista planuri sa fie
|
||||
activate** — nu s-a gasit nicio dovada de cod ca ar fi in curs, dar nici o confirmare ca sunt
|
||||
abandonate definitiv (in afara de `50`, marcat explicit "in lucru" in sursa). **De intrebat pe
|
||||
Marius direct**, pentru ca raspunsul schimba daca tabelul de configurare (§5) trebuie sa le includa
|
||||
preventiv sau poate ramane cu 23 de randuri.
|
||||
4. **Coloana `cSerie` pe gridul unificat — cost de adaugare** — prototipul n-are azi conceptul deloc;
|
||||
adaugarea ei presupune modificari de grid (nu doar de cod), posibil in afara perimetrului text-only
|
||||
(`.vc2`/`.sc2` prin `txt2vcx.ps1`) daca structura de coloane a gridului cere editare in IDE — **de
|
||||
confirmat la implementare**, nu presupus ca e o simpla proprietate.
|
||||
5. **Tabel de configurare ca `DBF` static vs. `INSERT`-uri in cod** — recomandarea (§5) e DBF, pentru
|
||||
editabilitate fara recompilare, dar suita foloseste azi predominant cod pentru acest fel de
|
||||
configurare (niciun precedent de DBF static de configurare gasit in `frm_facturare_articole`/
|
||||
`frm_date_factura`) — **de validat cu Marius daca abaterea de la conventia existenta merita
|
||||
beneficiul**, sau daca un cursor generat in cod (mai aproape de stilul actual, dar mai putin
|
||||
editabil) e preferat.
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare + proiectare incheiata in aceasta sesiune, fara sa fie nevoie de predare de context.
|
||||
Toate cele zece sectiuni cerute de misiune sunt completate, cu `fisier:linie` verificat direct pe
|
||||
fisierele text reale (`ofacturare.vc2`, `ofacturare.prg`, nu `.bak`), plus meniurile `.mn2` si
|
||||
raportul S4/S4b/S4_punct2 citate ca sursa pentru punctele deja stabilite (nereinvestigate).
|
||||
|
||||
Descoperiri dincolo de cerinta explicita a misiunii, semnalate clar in text: (1) patru tipuri lipsa
|
||||
din `Do Case`, nu unul (45/48/49/52, sectiunea 2); (2) prototipul (`frm_facturare_articole2.Init`)
|
||||
e aproape complet gol pe diferentiere de tip, nu doar incomplet (sectiunea 3) — cea mai mare
|
||||
descoperire a acestei sesiuni, cu impact direct asupra efortului de implementare estimat pentru S5;
|
||||
(3) doua divergente noi de rutare a cursorului (52, 24), pe langa cea deja cunoscuta (23) (sectiunea
|
||||
3); (4) tipurile 26 si 52 confirmate fara niciun bookkeeping `crsarticole`, inchizand punctul lasat
|
||||
deschis de raportul S4 punctul 2 (sectiunea 1, nota de subsol pe tabel).
|
||||
|
||||
Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, nicio scriere pe Oracle (numai
|
||||
`Read`/`Grep`/`vfp_symbols.ps1` pe fisiere de pe disc).
|
||||
321
docs/cercetare/s5b_proforma_descarcare_gestiune.md
Normal file
321
docs/cercetare/s5b_proforma_descarcare_gestiune.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# Cercetare — Verificarea 4: descarcare de gestiune pe proforma (Oracle)
|
||||
|
||||
Investigatie read-only, 10.08.2026. Nicio modificare de cod, niciun `git_sync.ps1`. Continua
|
||||
`docs\cercetare\proforma_copiere_puncte_intrare.md` (nu il reia) si raspunde punctual la
|
||||
"Necunoscute ramase" #1 de acolo.
|
||||
|
||||
**Nota sursa Oracle**: `docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (mentionat in brief) nu
|
||||
exista in `docs\`. Fisierul real e la
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (823 337 octeti,
|
||||
confirma marimea asteptata). Am semnalat discrepanta catre team-lead prin mesaj si am continuat pe
|
||||
acest fisier — **toate citatele `PACK:linie` de mai jos sunt pe fisierul din `DATABASE`, nu pe unul
|
||||
din `docs\`**, pentru ca al doilea nu exista.
|
||||
|
||||
## Verdict (raspuns la intrebarea 2)
|
||||
|
||||
**NU — la emiterea unei proforme, gestiunea nu se descarca**, pentru niciun articol, indiferent daca
|
||||
articolul e cu adevarat gestionabil in nomenclator sau nu. Blocajul e prin design: VFP marcheaza
|
||||
toate liniile unei proforme "negestionabile" inainte de compunerea documentului, ceea ce le trimite
|
||||
la server cu sentinela `id_gestiune = -1000` (dedus, nu confirmat linie-cu-linie — vezi sectiunea
|
||||
"Ce nu s-a putut stabili"), iar pe Oracle `contabilizeaza_articol` sare apelul catre
|
||||
`descarca_gestiune` exact pe acest sentinel. Concluzia se sprijina si pe intentia de business
|
||||
explicita din changelog (12.03.2021 / 2.7.x): *"Proforma. Articolele din proforma sunt marcate
|
||||
'negestionabile', astfel incat nu mai este necesara existenta articolelor in stoc pentru a genera
|
||||
proforma."* — adica cerinta initiala a fost explicit "proforma trebuie sa mearga si fara stoc",
|
||||
nu doar "nu arata plafonul".
|
||||
|
||||
---
|
||||
|
||||
## 1. Tipurile de document "proforma"
|
||||
|
||||
Deja stabilit in `proforma_copiere_puncte_intrare.md` §1 si reconfirmat aici pe partea Oracle:
|
||||
proforma **nu** e o valoare in `TIP` (`pack_facturare.ntip`, 1-52) — e un atribut ortogonal.
|
||||
|
||||
- **VFP**: `poDate.nIdTipDoc = 23` (fata de `5` = FACTURA), setat din combo-ul "Tip document"
|
||||
(`COMUN\clase\ofacturare.vc2:9396-9403`, `:8745-8754`). Setter-ul deriva boolean-ul
|
||||
`poDate.eProforma`: `COMUN\programe\ofacturare_comun.prg:593-599` —
|
||||
`This.eProforma = IIF(m.tnIdTipDoc = This.nIdTipDocProforma, 1, 0)`.
|
||||
- **Oracle**: nu exista `nIdTipDoc` — documentul se scrie in `VANZARI` cu `TIP` normal (business
|
||||
type, 1-52), iar `EPROFORMA` e o coloana separata pe `VANZARI`. Dovada directa:
|
||||
`PACK:5637-5671` (`PROCEDURE scrie_proforma`) — scrie intai antetul cu `scrie_in_vanzari` (acelasi
|
||||
helper ca la o factura normala), apoi:
|
||||
```
|
||||
PACK:5666-5669
|
||||
-- completez vanzari.eproforma
|
||||
update vanzari
|
||||
set eproforma = 1
|
||||
where id_vanzare = pack_facturare.nid_vanzare;
|
||||
```
|
||||
Deci pe Oracle, proforma **e** o factura normala in `VANZARI`/`VANZARI_DETALII`, cu un flag in
|
||||
plus.
|
||||
- Exista si un mecanism **legacy**, tabele separate `PROFORME`/`PROFORME_DETALII`
|
||||
(`PACK:5674-5767`, `PROCEDURE scrie_proforma_old` / `sterge_proforma_old`, `:5413-5429`) — nefolosit
|
||||
de fluxul curent (VFP apeleaza `scrie_proforma`, nu `scrie_proforma_old`; nu am gasit niciun apel
|
||||
VFP catre varianta `_old` in `COMUN\programe\*.prg`). Tratati ca schela moarta, nu ca mecanism activ.
|
||||
- `V_TIP = -102` in `citeste_setari_document` (`PACK:1960-1994`, ramura `:1985-1987`,
|
||||
`V_VARNAME := 'ID_FDOC_PROFORMA'`) e un cod folosit **doar** pentru alocarea de serie/numar
|
||||
(`id_fdoc`), apelat din VFP la `initializeaza_setari_document(-102)`
|
||||
(`COMUN\clase\ofacturare.vc2:9415`, deja in cercetarea anterioara) — nu are legatura cu
|
||||
`pack_facturare.ntip`, care ramane tipul de business real al documentului.
|
||||
|
||||
## 2. Descarcarea de gestiune la proforma — DA/NU si mecanismul exact
|
||||
|
||||
**NU.** Trasat pe trei straturi, VFP si Oracle:
|
||||
|
||||
### 2.1 VFP: articolele devin "negestionabile" inainte sa intre pe document
|
||||
|
||||
`COMUN\programe\ofacturare.prg:330-336` (in `factureaza`, imediat dupa incarcarea cursorului sursa
|
||||
`crsarticole`, indiferent de sursa — lista de preturi, comanda, contract, aviz, retur):
|
||||
```
|
||||
* Daca este o proforma, consider toate articolele negestionabile, pentru a nu mai alege din stoc
|
||||
* 12.03.2021
|
||||
IF poDate.eProforma = 1
|
||||
UPDATE (m.lcCursor) SET gestionabil = 0
|
||||
GO TOP IN (m.lcCursor)
|
||||
ENDIF
|
||||
```
|
||||
`gestionabil = 0` se propaga in `crsfactura` prin `prelucreaza_facturacrs`
|
||||
(`COMUN\programe\ofacturare_comun.prg:1801-1810`, coloana `gestionabil` e in lista de INSERT).
|
||||
|
||||
La adaugarea/editarea unei linii pe formular, ramura pe `gestionabil` decide dialogul:
|
||||
`COMUN\clase\ofacturare.vc2:13803-13809`:
|
||||
```
|
||||
Case (poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45) && 45 = ROARESTAURANT
|
||||
ofrmadarticol = Createobject("frm_articol_factura", lnCantitateMax, .F.)
|
||||
```
|
||||
adica **acelasi dialog folosit pentru orice articol negestionabil in restul aplicatiei** (nu unul
|
||||
proforma-specific), care ocoleste `do_alege_stoc` (dialogul de alegere lot din stoc). Pentru
|
||||
articolele care intra pe `frm_articol_factura`, `id_gestiune` implicit e sentinela `-1000`
|
||||
(`COMUN\clase\ofacturare.vc2:13618-13623`, `do_initializeaza_articol`:
|
||||
`If Type('toArticol.id_gestiune') = "U" THEN AddProperty(toArticol,'id_gestiune',-1000)`; acelasi
|
||||
tipar la `:17836-17837`). La scriere, `poArt.id_gestiune` se trimite direct ca parametru
|
||||
`V_ID_GESTIUNE` catre `pack_facturare.adauga_articol_factura`
|
||||
(`COMUN\clase\ofacturare.vc2:14069-14073`: `... + Nvl(Alltrim(Str(poArt.id_gestiune)),[NULL]) + ...`).
|
||||
|
||||
### 2.2 Oracle: `id_gestiune = -1000` e sentinela care blocheaza descarcarea
|
||||
|
||||
`adauga_articol_factura` (`PACK:4989-5284`), primeste `V_ID_GESTIUNE` si il traduce:
|
||||
```
|
||||
PACK:5032-5034
|
||||
IF V_ID_GESTIUNE <> -1000 THEN
|
||||
V_ID_GESTIUNE2 := V_ID_GESTIUNE;
|
||||
END IF;
|
||||
```
|
||||
`V_ID_GESTIUNE2` (necompletat, deci `NULL`, cand `V_ID_GESTIUNE = -1000`) e cel scris efectiv in
|
||||
`VANZARI_DETALII_TEMP.ID_GESTIUNE` (`PACK:5237,5267`).
|
||||
|
||||
La emitere, `contabilizeaza_articol` (`PACK:7173-7547`) parcurge liniile din
|
||||
`VANZARI_DETALII_TEMP` si apeleaza `descarca_gestiune` doar aici:
|
||||
```
|
||||
PACK:7472-7476
|
||||
IF pack_facturare.ntip <> 4 THEN
|
||||
IF pack_facturare.nscadere_stoc = 1 AND
|
||||
detalii_articol.id_gestiune <> -1000 AND
|
||||
detalii_articol.in_stoc = 1 THEN
|
||||
pack_facturare.descarca_gestiune(...)
|
||||
```
|
||||
`NULL <> -1000` evalueaza la `NULL` in PL/SQL (nu `TRUE`), deci conditia pica indiferent de
|
||||
`in_stoc` — **liniile de pe o proforma nu ajung niciodata la `descarca_gestiune`**, cata vreme
|
||||
`id_gestiune` a intrat ca `-1000`.
|
||||
|
||||
Exista si o a treia bariera, independenta, **in interiorul** lui `descarca_gestiune`
|
||||
(`PACK:7648-7797`), dar aceasta priveste articolul insusi, nu documentul:
|
||||
```
|
||||
PACK:7789-7797
|
||||
-- NU SE DESCARCA GESTIUNEA PENTRU ARTICOLELE NEGESTIONABILE
|
||||
-- ASTFEL INCAT SA SE POATA FACE OPERATII GEN AVIZ DIN CUSTODIE INCLUSIV CU ARTICOLE NEGESTIONABILE
|
||||
SELECT MAX(IN_STOC) INTO lnInStoc FROM NOM_ARTICOLE WHERE ID_ARTICOL = V_ID_ARTICOL;
|
||||
IF lnInStoc = 0 THEN GOTO SFARSIT; END IF;
|
||||
```
|
||||
Aceasta verifica `NOM_ARTICOLE.IN_STOC` real (nomenclator), nu flagul de proforma — protejeaza un
|
||||
articol cu adevarat negestionabil, indiferent de tipul documentului. **Nu** e mecanismul care
|
||||
protejeaza proforma; mecanismul de proforma e strict gate-ul `id_gestiune <> -1000` de la 2.1-2.2.
|
||||
|
||||
### 2.3 `in_stoc` trimis de VFP: uneori ignorat de Oracle, dar nu e el gate-ul decisiv
|
||||
|
||||
`V_IN_STOC_TEMP` (parametrul care ajunge in `VANZARI_DETALII_TEMP.IN_STOC`) e **fie** preluat direct
|
||||
de la VFP (`V_IN_STOC := V_IN_STOC_TEMP`, ramurile implicita si restaurant, `PACK:5200-5203,
|
||||
5111-5112`), **fie recalculat de Oracle din nomenclator/contract**, ignorand ce a trimis VFP, pe
|
||||
ramurile comenzi (`PACK:5061-5066`), avize (`:5086-5091`) si contract cu pret de contract
|
||||
(`:5153-5158, 5184`). Asta inseamna ca pentru o proforma facuta din comanda/aviz/contract, campul
|
||||
`IN_STOC` scris efectiv poate reveni la valoarea reala din nomenclator (`1` pentru un articol
|
||||
gestionabil), **dar** asta nu conteaza — gate-ul din 2.2 cere `id_gestiune <> -1000 AND in_stoc = 1`
|
||||
cu **AND**, iar `id_gestiune` ramane blocat la `-1000`/`NULL` indiferent de sursa documentului
|
||||
(sentinela vine din UI, la nivelul liniei, nu din cursorul de incarcare). Deci recalcularea lui
|
||||
`in_stoc` de catre Oracle pe aceste ramuri nu redeschide descarcarea.
|
||||
|
||||
## 3. De la proforma la factura
|
||||
|
||||
Nu exista o rutina de "transformare" dedicata — mecanismul e **copierea**, deja documentata complet
|
||||
in `proforma_copiere_puncte_intrare.md` §2 (tooltip explicit `COMUN\clase\ofacturare_comun.vc2:1409`:
|
||||
*"Se foloseste si pentru generarea unei facturi din proforma prin copiere"*).
|
||||
|
||||
- **Punct de intrare VFP**: `frm_facturi.But_copiaza1.Click -> do_copiaza` (degradeaza tipul de
|
||||
business, `COMUN\clase\ofacturare_comun.vc2:3628-3713`) `-> copiere_factura`
|
||||
(`COMUN\programe\oproceduri_facturare.prg:150-153`) `-> factureaza(tip_degradat, toFactura)`.
|
||||
- **Punct de intrare Oracle**: acelasi `pack_facturare.adauga_articol_factura` / `scrie_in_vanzari`
|
||||
ca la orice document nou — nu exista un `pack_facturare.transforma_proforma` sau echivalent.
|
||||
Singurul apel Oracle specific copierii e cursorul de precompletare,
|
||||
`pack_facturare.cursor_retur_document` (vezi §4), apelat cu `V_COPIERE = 1`
|
||||
(`COMUN\programe\ofacturare.prg:267-268`).
|
||||
- `completeaza_setari_document(toDateAnterior, .T.)` (`COMUN\programe\ofacturare_comun.prg:362-412`)
|
||||
**nu propaga `nIdTipDoc`** (linia comentata, `:370`) — documentul nou porneste cu `nIdTipDoc`
|
||||
implicit (`5`=FACTURA sau `6`=AVIZ, dupa `tnTip` degradat, `COMUN\programe\ofacturare.prg:187-196`),
|
||||
deci **`poDate.eProforma = 0` pentru documentul nou**, indiferent ca sursa era proforma.
|
||||
|
||||
## 4. Ce se intampla cu stocul intre proforma si factura
|
||||
|
||||
**Nimic de reconciliat, pentru ca proforma nu a atins niciodata stocul** (§2). Nu exista concept de
|
||||
"rezervare de stoc" pe proforma:
|
||||
- tabelele legacy `PROFORME`/`PROFORME_DETALII` (§1) nu au coloane de rezervare si oricum nu sunt pe
|
||||
calea activa;
|
||||
- nu am gasit, in `PACK_FACTURARE`, niciun apel care sa insereze in `RUL` sau sa actualizeze
|
||||
`STOC` la `scrie_proforma` — funcita se limiteaza la `scrie_in_vanzari` + `UPDATE vanzari SET
|
||||
eproforma=1` (§1, `PACK:5637-5671`);
|
||||
- cautare directa in fisierul PACK pentru orice mentiune de rezervare pe proforma (`rezerv`,
|
||||
`blocheaza stoc`) nu a dat rezultate relevante (v. "Ce nu s-a putut stabili" pentru limitele
|
||||
cautarii text simple pe un fisier de 823 KB).
|
||||
|
||||
**La copiere (transformarea efectiva in factura)**, gestionabilitatea reala se **restaureaza**:
|
||||
cursorul de precompletare la copiere, `cursor_retur_document`
|
||||
(`PACK:3949-4000`), calculeaza `GESTIONABIL` asa:
|
||||
```
|
||||
PACK:3993-4000
|
||||
(case
|
||||
when V_PROFORMA = 1 then 0
|
||||
when V_COPIERE = 1 then B.IN_STOC -- NOM_ARTICOLE.IN_STOC, articolul real
|
||||
else A.GESTIONABIL
|
||||
end) AS GESTIONABIL,
|
||||
```
|
||||
La copiere, `V_COPIERE = 1` e hardcodat (`COMUN\programe\ofacturare.prg:267-268`:
|
||||
`cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)` — al treilea
|
||||
parametru pozitional `1` e `V_COPIERE`), iar `V_PROFORMA` trimis e `poDate.eProforma` **al
|
||||
documentului nou**, care e `0` (§3) — deci ramura `V_PROFORMA=1` nu se activeaza la copiere, si
|
||||
`GESTIONABIL = B.IN_STOC` (flagul real din nomenclator). Rezultat: liniile copiate dintr-o proforma
|
||||
redevin gestionabile normal daca articolul chiar e in stoc, trec prin `do_alege_stoc` la editare
|
||||
(`ofacturare.vc2:13804`, ramura `Otherwise`), primesc un `id_gestiune` real, si **descarcarea de
|
||||
gestiune se face normal la emiterea facturii rezultate din copiere** — exact ca la orice factura noua,
|
||||
nu printr-un pas separat de "transformare".
|
||||
|
||||
## 5. Ce verifica serverul la descarcare, si cu ce eroare
|
||||
|
||||
Verificari observate direct in `PACK_FACTURARE`, toate independente de proforma (se aplica oricarei
|
||||
descarcari reale de gestiune):
|
||||
- **Gestiune inexistenta/stearsa**: `PACK:7847-7858` — `SELECT ... FROM NOM_GESTIUNI WHERE
|
||||
ID_GESTIUNE = V_ID_GESTIUNE AND STERS = 0`; `NO_DATA_FOUND -> FACT-007`.
|
||||
- **Stoc epuizat / lot inexistent**: `PACK:7860-8493` — construieste `tab_stoc` din `RUL`/`STOC` dupa
|
||||
criterii (articol, gestiune, cont, serie, pret etc.) si, daca nu gaseste nimic
|
||||
(`SQL%ROWCOUNT = 0`): `FACT-008` ("Articolul ... nu mai e in stoc") sau, daca nici macar
|
||||
denumirea nu se gaseste, `FACT-009`.
|
||||
- Ramuri similare mai jos in acelasi fisier pentru alte cazuri de gestiune/combinatii invalide:
|
||||
`FACT-010` (`:10001`), `FACT-011` (`:10323`), `FACT-014` combinatie invalida de gestiuni
|
||||
(`:12127`), `FACT-019` gestiune (`:10449`), `FACT-020`/`FACT-021` variante "nu mai e in stoc"
|
||||
(`:10748,10752`).
|
||||
- **Optiune globala de bypass**: `RF_FACTURARE_FARA_STOC` (`PACK:7783-7784`,
|
||||
`lnFacturareFaraStoc`) — permite facturarea peste cantitatea disponibila pentru articole
|
||||
gestionabile; **nu e specifica proformei**, e o optiune de firma generala.
|
||||
- **Cota TVA lipsa / articol fara politica de pret** — nu sunt verificari de stoc, dar sunt cele mai
|
||||
frecvente erori vecine (`FACT-024` la `contabilizeaza_articol`, `PACK:7278-7302`; `FACT-018`,
|
||||
`FACT-012`, `FACT-013` la cautarea cotei TVA in `adauga_articol_factura`, deja semnalate in
|
||||
`plan_13_unificare_formular_facturare.md` §G). O proforma cu `id_gestiune=-1000` **nu trece deloc**
|
||||
prin verificarile de stoc de mai sus (FACT-007/008/009/010/011/014/019/020/021), pentru ca nu ajunge
|
||||
la `descarca_gestiune` — dar tot trece prin verificarea de politica de pret / cota TVA, care nu are
|
||||
legatura cu stocul.
|
||||
|
||||
## 6. Consecinte pentru S5b — constrangeri de proiectare
|
||||
|
||||
1. **Formularul unificat trebuie sa reproduca exact mecanismul `gestionabil=0` la incarcarea
|
||||
liniilor cand `poDate.eProforma = 1`** — nu doar sa ascunda vizual plafonul. Fara acest pas,
|
||||
articolele gestionabile ar intra pe document cu `id_gestiune` real si ar declansa
|
||||
`descarca_gestiune` la emitere, contrazicand comportamentul de azi si cerinta de business
|
||||
("proforma merge fara stoc").
|
||||
2. **Nu se apeleaza `do_alege_stoc` (dialogul de alegere lot) pentru liniile unei proforme.** Traseul
|
||||
corect e cel al articolului negestionabil (`frm_articol_factura`/`do_initializeaza_articol`), care
|
||||
garanteaza `id_gestiune = -1000` pe linie.
|
||||
3. **`id_gestiune = -1000` trebuie sa ajunga efectiv in parametrul `V_ID_GESTIUNE` trimis catre
|
||||
`pack_facturare.adauga_articol_factura`** — verificarea de gate e strict pe aceasta valoare
|
||||
(`<> -1000`), nu pe un flag de document. Daca formularul unificat schimba felul in care
|
||||
construieste liniile (de ex. reutilizeaza un obiect de linie comun facturii si proformei), acest
|
||||
`-1000` trebuie sa fie explicit setat pe ramura `eProforma=1`, nu mostenit implicit.
|
||||
4. **`in_stoc`/`gestionabil` trimis de VFP nu e suficient de la sine** — pe unele surse (comanda,
|
||||
aviz, contract cu pret de contract) Oracle il **rescrie** din nomenclator/contract (§2.3). Gate-ul
|
||||
real e `id_gestiune`, deci formularul unificat nu poate conta pe faptul ca a trimis `in_stoc=0`;
|
||||
trebuie sa garanteze `id_gestiune=-1000`.
|
||||
5. **La copiere proforma -> factura, comportamentul trebuie sa fie opus**: liniile trebuie sa-si
|
||||
recapete gestionabilitatea reala (`B.IN_STOC` din nomenclator), nu sa ramana blocate la
|
||||
negestionabil. Decizia deja luata in plan ("degradarea de tip din `do_copiaza` ramane pentru
|
||||
copiere si nu se aplica la regenerare") e consistenta cu asta, dar merita spus explicit: **regula
|
||||
`eProforma=1 -> gestionabil=0` se aplica doar la incarcarea/compunerea unei proforme noi, nu si la
|
||||
copierea din ea** — documentul nou pleaca cu `eProforma=0` (§3-4) si trebuie sa lase Oracle sa
|
||||
recalculeze `GESTIONABIL` normal.
|
||||
6. **Formularul unificat nu trebuie sa implementeze nicio logica de "eliberare stoc rezervat" la
|
||||
trecerea proforma -> factura** — nu exista rezervare de stoc pe proforma (§4), deci nu exista
|
||||
nimic de eliberat. Riscul tehnic real ramane cel deja semnalat in plan §G (ordinea
|
||||
stergere-inaintea-reemiterii in aceeasi tranzactie la regenerare), care e independent de proforma.
|
||||
7. **Verificarile de stoc (`FACT-007/008/009/010/011/014/019/020/021`) nu se vor manifesta niciodata
|
||||
pentru o linie de proforma** cata vreme regula #1-#3 e respectata — formularul unificat nu are
|
||||
nevoie de tratament special pentru aceste coduri de eroare pe ramura proforma (nu pot aparea
|
||||
acolo), dar tot trebuie sa trateze erorile de politica de pret/TVA (`FACT-012/013/018/024`), care
|
||||
raman valabile si pe proforma.
|
||||
|
||||
## 7. Copierea proformei in formularul unificat — ce lipseste
|
||||
|
||||
Plecand de la `proforma_copiere_puncte_intrare.md` §2 (traseul general de copiere, deja complet
|
||||
documentat) si de la S5b din plan (`plan_13_unificare_formular_facturare.md:1674-1689`):
|
||||
|
||||
- **Ce exista deja si se reutilizeaza neschimbat**: `do_copiaza` (degradarea de tip),
|
||||
`copiere_factura`, `factureaza(tip, toFactura)`, `completeaza_setari_document`,
|
||||
`cursor_retur_document` cu `V_COPIERE=1`. Niciunul din aceste puncte de intrare nu are legatura
|
||||
speciala cu proforma dincolo de parametrul `V_PROFORMA` deja tratat corect (§4) — nu trebuie
|
||||
adaugat nimic nou aici pentru ca formularul unificat sa suporte copierea unei proforme.
|
||||
- **Ce lipseste, specific formularului unificat, nu copierii in sine**: mecanismul `crsarticole`
|
||||
intreg (incarcarea de masa + `UPDATE ... SET gestionabil=0`, §2.1) presupune un cursor complet
|
||||
incarcat inainte de afisare. Planul (`plan_13_unificare_formular_facturare.md:1470`) stabileste deja
|
||||
ca `crsarticole` **nu se mai incarca in masa** in formularul unificat — deci pasul "seteaza
|
||||
gestionabil=0 pe toate liniile cand eProforma=1" **trebuie reimplementat linie-cu-linie**, la
|
||||
momentul in care fiecare linie e adaugata pe formularul unificat (fie la copiere, fie la compunere
|
||||
noua), nu ca un singur `UPDATE` de masa pe un cursor care nu mai exista. Constrangerea #1-#3 de mai
|
||||
sus (sectiunea 6) e exact specificatia acestui pas lipsa.
|
||||
- **Combo-ul de tip document si realocarea de serie** — deja acoperite de decizia S5b din plan
|
||||
(`Ct_clb_fdoc` ramane, realocare la comutare); nu am gasit nimic suplimentar de adaugat aici fata
|
||||
de ce e deja scris in plan.
|
||||
|
||||
## Ce nu s-a putut stabili
|
||||
|
||||
1. **Linia exacta unde `poArt.id_gestiune` devine efectiv `-1000` pentru o linie de proforma**, in
|
||||
loc de a ramane la valoarea implicita de camp (`0`, cursorul `crsfactura` creat cu `id_gestiune
|
||||
N(20)` fara `NULL`, iar `prelucreaza_facturacrs` nu include `id_gestiune` in lista sa de INSERT —
|
||||
`COMUN\programe\ofacturare_comun.prg:1801-1810`). Am gasit sentinela `-1000` setata **conditionat**
|
||||
("daca proprietatea lipseste") in `do_initializeaza_articol`
|
||||
(`COMUN\clase\ofacturare.vc2:13618-13623`), dar nu am confirmat ca proprietatea chiar "lipseste"
|
||||
(`Type = 'U'`) in momentul in care `frm_articol_factura` proceseaza o linie de proforma provenita
|
||||
din `Scatter` pe `crsfactura`. **Argument indirect, nu dovada directa**: daca `id_gestiune` ar
|
||||
ajunge `0` (nu `-1000`) la server, `descarca_gestiune` ar cauta `NOM_GESTIUNI WHERE ID_GESTIUNE=0`
|
||||
si ar arunca `FACT-007` la fiecare emitere de proforma cu articol gestionabil din comanda/aviz —
|
||||
eroare care ar fi vizibila si raportata de ani (proforma exista din 2014+ in changelog); absenta
|
||||
oricarei asemenea raportari sustine indirect ca sentinela `-1000` chiar ajunge la server, dar nu e
|
||||
o dovada pe cod.
|
||||
2. **Nicio verificare pe date vii** — tot ce e mai sus e trasare de cod static (VFP text + PL/SQL
|
||||
text), nu rulare/log real. Nu am rulat nimic, conform mandatului read-only.
|
||||
3. **Cautarea de "rezervare de stoc pe proforma"** (§4) s-a facut prin grep text simplu pe
|
||||
`PACK_FACTURARE` dupa cuvinte cheie (`rezerv`, `PROFORMA`) — nu e o dovada de completitudine
|
||||
pentru intreaga baza de date Oracle (declanșatoare/triggere pe `VANZARI`/`VANZARI_DETALII`,
|
||||
proceduri din alte pachete). Zero rezultate nu inseamna cu certitudine ca nu exista niciun
|
||||
mecanism de rezervare in alta parte a schemei.
|
||||
4. **Fisierul `docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`** nu a fost creat de mine — am lucrat
|
||||
pe originalul din `DATABASE\SCRIPTURI_CLAR`. Daca cineva copiaza ulterior fisierul in `docs\` cu
|
||||
alta numerotare de linii, citatele `PACK:linie` din acest raport trebuie re-verificate pe copia
|
||||
noua.
|
||||
|
||||
## Verificari recomandate pe date vii (daca raman intrebari)
|
||||
|
||||
- Emis o proforma de test cu un articol **cu adevarat gestionabil si cu stoc real**, sursa = comanda
|
||||
(ramura unde Oracle rescrie `IN_STOC`, §2.3) — confirma ca `RUL`/`STOC` nu se modifica dupa emitere
|
||||
si ca `VANZARI_DETALII.ID_GESTIUNE` a fost scris `NULL` pentru acea linie.
|
||||
- Acelasi test, sursa = lista de preturi (ramura unde Oracle are incredere in `V_IN_STOC_TEMP`) —
|
||||
pentru comparatie.
|
||||
- Copiaza proforma de mai sus in factura si confirma ca `VANZARI_DETALII.ID_GESTIUNE` al facturii
|
||||
rezultate e populat cu o gestiune reala si ca `RUL` inregistreaza descarcarea la emiterea facturii.
|
||||
- Interogare directa pe schema pentru triggere/joburi legate de `EPROFORMA` sau de rezervare de stoc,
|
||||
daca exista suspiciunea din punctul 3 de mai sus:
|
||||
`SELECT trigger_name, table_name FROM user_triggers WHERE table_name IN ('VANZARI','VANZARI_DETALII','STOC','RUL');`
|
||||
610
docs/cercetare/s5b_proiectare_proforma_copiere.md
Normal file
610
docs/cercetare/s5b_proiectare_proforma_copiere.md
Normal file
@@ -0,0 +1,610 @@
|
||||
# Proiectare S5b — proforma si copierea pe formularul unificat
|
||||
|
||||
Cercetare + proiectare READ-ONLY (fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara
|
||||
commit, fara scriere Oracle — numai `SELECT`), pentru povestea **S5b** din
|
||||
`docs\plan_13_unificare_formular_facturare.md` (sectiunea `#### S5b`, liniile 2507-2530), deciziile
|
||||
10 si 11. Continua, fara sa reia, `docs\cercetare\s5b_proforma_descarcare_gestiune.md` (sursa de
|
||||
adevar pentru mecanismul `gestionabil=0` / `id_gestiune=-1000`) si foloseste rezultatele din
|
||||
`docs\cercetare\s4e_lista_preturi_pe_sursa.md` (coliziunea `id_c`) si
|
||||
`docs\cercetare\s4_punct2_registru_cantitate_ramasa.md` (Rol A/B ale `crsarticole`).
|
||||
|
||||
**Status: cercetare + proiectare incheiate.**
|
||||
|
||||
Nu se ating `COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg`
|
||||
(perimetrul altei sarcini). `COMUN\programe\ofacturare.prg` si `ofacturare_comun.prg` sunt citite,
|
||||
nu editate — la fel `COMUN\clase\ofacturare.vc2` si `COMUN\clase\ofacturare_comun.vc2` (citire, nu
|
||||
editare; scriere interzisa doar pe al doilea).
|
||||
|
||||
---
|
||||
|
||||
## Descoperire centrala, care rescrie premisa de risc a lui S5b
|
||||
|
||||
Sursa de adevar anterioara (`s5b_proforma_descarcare_gestiune.md`) a stabilit ca gate-ul pentru
|
||||
proforma e sentinela `id_gestiune = -1000`, verificata **in interiorul** lui
|
||||
`contabilizeaza_articol` (`PACK:7472-7476`). Cercetarea de fata a gasit un strat **mai devreme si
|
||||
mai tare**: la `Termina`, `do_scrie_factura` **alege intre doua proceduri Oracle diferite** dupa
|
||||
`poDate.eProforma`, inainte sa se uite la `poDate.tip` (`COMUN\clase\ofacturare.vc2:14282-14300`):
|
||||
|
||||
```
|
||||
Do Case
|
||||
Case poDate.eProforma = 1
|
||||
* scrie_proforma are aceiasi parametri ca scrie_factura2
|
||||
* salveaza doar in vanzari, nu si in contabilitate
|
||||
lcSql = [{call pack_facturare.scrie_proforma(...)}]
|
||||
Case poDate.Tip = 4
|
||||
... lcSql = [{call pack_facturare.scrie_factura_avize(...)}]
|
||||
Case Inlist(poDate.Tip,3,21,25,28,42,47)
|
||||
... lcSql = [{call pack_facturare.scrie_factura2(...)}]
|
||||
Otherwise
|
||||
... lcSql = [{call pack_facturare.scrie_factura2(...)}]
|
||||
Endcase
|
||||
```
|
||||
|
||||
Comentariul din cod ("salveaza doar in vanzari, nu si in contabilitate") e literal adevarat, verificat
|
||||
pe corpul Oracle: `pack_facturare.scrie_proforma` (`PACK:5637-5671`) cheama **doar**
|
||||
`scrie_in_vanzari` (`PACK:13488-13760`) — care face `INSERT INTO VANZARI` (antet) **si**
|
||||
`INSERT INTO VANZARI_DETALII ... SELECT FROM VANZARI_DETALII_TEMP` (liniile, cu `ID_GESTIUNE` asa
|
||||
cum a ajuns in `TEMP`) — apoi marcheaza `VANZARI.EPROFORMA=1`. **`scrie_proforma` nu cheama
|
||||
niciodata `contabilizeaza_articol`.** `scrie_factura2`/`scrie_factura_avize`/`scrie_aviz_retur` sunt
|
||||
singurele trei proceduri care cheama `contabilizeaza_articol` (confirmat si in
|
||||
`docs\plan_13_unificare_formular_facturare.md:298-310`, runda 11) — si niciuna din ele nu ruleaza
|
||||
pentru `eProforma=1`.
|
||||
|
||||
**Consecinta:** pentru o proforma, `scrie_nota` (care scrie `NOTE_CONTABILE`) si `descarca_gestiune`
|
||||
nu ruleaza **deloc** — nu pentru ca sentinela `-1000` le blocheaza pe fiecare linie (desi si asta
|
||||
ramane adevarat, ca plasa suplimentara), ci pentru ca **intreaga functie care le cheama nu se
|
||||
executa**. Sentinela `-1000` din `contabilizeaza_articol` conteaza doar cand `contabilizeaza_articol`
|
||||
chiar ruleaza — adica exact pe drumul invers (vezi sectiunea 5), nu pe proforma insasi.
|
||||
|
||||
**De ce conteaza pentru S5b**: alegerea `scrie_proforma` vs. `scrie_factura2` se face **o singura
|
||||
data, la Termina**, citind `poDate.eProforma` **in acel moment** — nu depinde de cand/cum a fost
|
||||
comutat combo-ul, nici de valorile `gestionabil`/`id_gestiune` de pe liniile individuale. Asta e o
|
||||
veste buna pentru un sens al comutarii (proforma -> ramane proforma la salvare: corect, indiferent ce
|
||||
au liniile), dar **nu acopera** sensul opus (liniile marcate negestionabil sub proforma, apoi
|
||||
documentul comutat inapoi la factura inainte de Termina) — acolo `scrie_factura2` **chiar** ruleaza,
|
||||
si atunci sentinela `-1000` de pe liniile ramase de la proforma **chiar blocheaza** `descarca_gestiune`
|
||||
pe un document care ar trebui sa descarce. Vezi sectiunea 5.
|
||||
|
||||
---
|
||||
|
||||
## 1. Fluxul de azi al proformei, cap-coada
|
||||
|
||||
**1.1 Alegerea tipului.** Proforma nu e o valoare in `pack_facturare.ntip` (tipul de business,
|
||||
1-52) — e un atribut ortogonal, `poDate.nIdTipDoc` (5=FACTURA, 23=PROFORMA, 3=BON FISCAL,
|
||||
6=AVIZ), setat din combo-ul "Tip document" (`Ct_clb_fdoc._combobox1`,
|
||||
`RowSource = "FACTURA,PROFORMA,BON FISCAL"`, `ofacturare.vc2:8745-8754`). Setter-ul
|
||||
`nIdTipDoc_Assign` (`COMUN\programe\ofacturare_comun.prg:593-600`) deriva boolean-ul in acelasi
|
||||
moment: `This.eProforma = IIF(m.tnIdTipDoc = This.nIdTipDocProforma, 1, 0)`.
|
||||
|
||||
**1.2 Marcarea in masa la incarcarea cursorului.** In `factureaza()` (`COMUN\programe\ofacturare.prg`),
|
||||
dupa ce cursorul sursa (indiferent care — lista de preturi, comanda, contract, aviz, retur) e adus in
|
||||
`crsarticole` (Do Case pe `tnTip`, `:266-311`), **daca** `poDate.eProforma = 1`, se face un `UPDATE`
|
||||
de masa peste tot cursorul (`:330-334`):
|
||||
```
|
||||
* Daca este o proforma, consider toate articolele negestionabile, pentru a nu mai alege din stoc
|
||||
IF poDate.eProforma = 1
|
||||
UPDATE (m.lcCursor) SET gestionabil = 0
|
||||
GO TOP IN (m.lcCursor)
|
||||
ENDIF
|
||||
```
|
||||
Acest pas ruleaza **o singura data**, la incarcare, **inainte** ca formularul de linii sa se
|
||||
deschida — pentru ca la momentul lui `poDate.eProforma` e deja finala: combo-ul traieste in
|
||||
`frm_date_factura`/`frm_date_aviz`, un dialog modal care se **inchide** (`ofrmceredate.Show()` la
|
||||
`:235`, urmat de `Release ofrmceredate` la `:248`) **inainte** ca `Do Case`-ul de incarcare a
|
||||
cursorului sa ruleze (`:266` e dupa `:248`). Tipul nu se mai poate schimba dupa acest punct, azi.
|
||||
|
||||
**1.3 Ce face `gestionabil=0` la nivel de linie.** Cand operatorul adauga o linie, ramura care alege
|
||||
dialogul citeste exact acest camp (`ofacturare.vc2:13803-13809`):
|
||||
```
|
||||
Do Case
|
||||
Case (poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45)
|
||||
ofrmadarticol = Createobject("frm_articol_factura", lnCantitateMax, .F.)
|
||||
Otherwise
|
||||
Thisform.do_alege_stoc(...)
|
||||
Endcase
|
||||
```
|
||||
`frm_articol_factura` e dialogul generic pentru orice articol negestionabil din toata aplicatia
|
||||
(nu unul specific proformei) — ocoleste `do_alege_stoc` (alegerea lotului din stoc).
|
||||
`do_initializeaza_articol` (`ofacturare.vc2:13618-13623`) seteaza `id_gestiune=-1000` **doar daca
|
||||
proprietatea lipseste** pe obiectul articol (`Type(...) = "U"`):
|
||||
```
|
||||
If Type('toArticol.id_gestiune') = "U"
|
||||
AddProperty(toArticol,'id_gestiune',-1000)
|
||||
Endif
|
||||
```
|
||||
La scriere, `poArt.id_gestiune` merge direct ca `V_ID_GESTIUNE` catre
|
||||
`pack_facturare.adauga_articol_factura` (`ofacturare.vc2:14069-14073`), care il traduce
|
||||
(`PACK:5032-5034`: `IF V_ID_GESTIUNE <> -1000 THEN V_ID_GESTIUNE2 := V_ID_GESTIUNE; END IF;` —
|
||||
altfel ramane `NULL`), scris in `VANZARI_DETALII_TEMP.ID_GESTIUNE`.
|
||||
|
||||
**Nota de incertitudine, mostenita din raportul-sursa**: linia exacta unde proprietatea
|
||||
`id_gestiune` "lipseste" (`Type='U'`) pentru o linie de proforma scatter-uita din `crsfactura` nu a
|
||||
fost confirmata direct pe date vii — `crsfactura` are `id_gestiune` ca si camp cu valoare implicita,
|
||||
nu absent. Ramane argument indirect (fara `FACT-007` raportat in productie de ani), nu dovada de
|
||||
executie. **De verificat cu prioritate la implementare**, pentru ca proiectarea de mai jos (sectiunea
|
||||
4) muta acest mecanism dintr-un `UPDATE` de masa intr-o marcare per-linie, si trebuie sa stie exact
|
||||
ce camp seteaza.
|
||||
|
||||
**1.4 Routingul la Termina.** Vezi "Descoperirea centrala" de mai sus: `scrie_proforma`, nu
|
||||
`scrie_factura2`/`scrie_factura_avize` — fara nota contabila, fara descarcare de gestiune, indiferent
|
||||
de tipul de business original.
|
||||
|
||||
**1.5 Raportul propriu.** `listeaza_ofacturare` alege raportul dupa `poDate.eProforma`
|
||||
(`ofacturare.prg:1638-1643`):
|
||||
```
|
||||
Case (Between(poDate.tip, 1, 20) Or Inlist(poDate.tip, -1,-2,-3,-4,-8,-11,44,45,48,49,50,51,52)) And poDate.eProforma = 1
|
||||
lcRaport = [PROFORMA]
|
||||
lcRaportVal = [PROFORMA_VAL]
|
||||
lcSetare = [PROFORMA]
|
||||
```
|
||||
Independent de forma formularului (unificat sau nu) — declansat de `poDate.eProforma` la momentul
|
||||
listarii, care e populata corect din `nIdTipDoc_Assign` daca antetul reflecta starea finala.
|
||||
|
||||
**1.6 Garzile `eProforma = 0` pe atasamente (nu pe nota contabila).** Cinci guarde separate in
|
||||
`listeaza_ofacturare`, toate de forma `poDate.nRelistare = 0 And poDate.eProforma = 0 ...`, controland
|
||||
salvarea PDF-ului ca atasament arhivat (`export2pdf(..., poDate.cDocAtasate)` si
|
||||
`poDate.scrieAtasamente()`), **nu** scrierea notei contabile (care e blocata la alt nivel, sectiunea
|
||||
precedenta): `ofacturare.prg:1960` (factura in lei), `:1996` (aviz retur), `:2052` (factura in
|
||||
valuta), `:2063` (recapitulatie), `:2103` (`scrieAtasamente()`, arhivarea propriu-zisa). **Corectie
|
||||
fata de formularea din plan** (`plan_13_unificare_formular_facturare.md:548-549`, "fara nota
|
||||
contabila si fara atasamente, prin garzile `eProforma=0`"): cele doua efecte sunt reale amandoua, dar
|
||||
prin **mecanisme diferite** — nota contabila prin routing-ul `scrie_proforma`/`scrie_factura2`
|
||||
(sectiunea "Descoperire centrala"), atasamentele prin aceste cinci guarde explicite. Nu schimba nimic
|
||||
pentru proiectare, dar conteaza pentru cine cauta "garda de nota contabila" in cod si nu o gaseste la
|
||||
liniile citate de plan.
|
||||
|
||||
**1.7 Relistarea pe cale separata.** `frm_facturi.do_listare` (`COMUN\clase\ofacturare_comun.vc2:7233-`)
|
||||
reconstruieste `poDate` de la zero pentru un document deja emis, cu `poDate.eProforma = 1` setat
|
||||
explicit (`:7262`) cand se relisteaza o proforma din grid — nu refoloseste obiectul `poDate` din
|
||||
sesiunea de facturare curenta.
|
||||
|
||||
---
|
||||
|
||||
## 2. Fluxul de azi al copierii, cap-coada
|
||||
|
||||
**2.1 Degradarea de tip in `do_copiaza`.** `frm_facturi.do_copiaza`
|
||||
(`COMUN\clase\ofacturare_comun.vc2:3628-3713`) mapeaza **orice** tip de business catre unul din
|
||||
cinci "simple" — `T1` (lista de preturi), `T10` (lista de preturi valuta), `T5` (invoice),
|
||||
`T22` (aviz din lista de preturi), sau `T1`/`T10` pentru transfer/altele — pe un `Do Case` explicit
|
||||
peste `loFactura.tip` (`:3693-3708`). Factura/avizul din contract sau din comanda **nu se copiaza ca
|
||||
atare** — devine o factura simpla din lista de preturi. Apoi `DO copiere_factura WITH loFactura IN
|
||||
oproceduri_facturare.prg` (`:3710`).
|
||||
|
||||
**2.2 `copiere_factura` -> `factureaza(tip, toFactura)`.** `copiere_factura`
|
||||
(`COMUN\programe\oproceduri_facturare.prg:150-153`) cheama `factureaza(loFactura.tip, loFactura)` —
|
||||
`toFactura` (obiectul scatter-uit din `crsFacturi`) devine parametrul opțional al lui `factureaza`
|
||||
(`COMUN\programe\ofacturare.prg:81-82`), care seteaza `llCopiere = (Type('toFactura') = 'O')`
|
||||
(`:111`).
|
||||
|
||||
**2.3 Antetul precompletat, dar `nIdTipDoc` NU se propaga.**
|
||||
`poDate.completeaza_setari_document(toFactura, .T.)` (`ofacturare.prg:204`, apelat cand `m.llCopiere`)
|
||||
cheama `oDateFactura::completeaza_setari_document`
|
||||
(`COMUN\programe\ofacturare_comun.prg:362-412`), ramura `tlFactura=.T.` (`:368-390`): copiaza delegat,
|
||||
masina, sectie, agent, client, referinta la documentul sursa (`listaid = toDateAnterior.id_vanzare`) —
|
||||
dar **linia `.nIdTipDoc = toDateAnterior.nIdTipDoc` e comentata** (`:370`,
|
||||
`*.nIdTipDoc = toDateAnterior.nIdTipDoc`), deliberat. Documentul nou primeste `nIdTipDoc` implicit
|
||||
dupa tipul degradat (`Do Case` la `ofacturare.prg:187-196`: `tnTip < 21` sau in
|
||||
`{45,48,49,51,52}` => `5` FACTURA, altfel `6` AVIZ) — pentru tipurile degradate ale copierii (`T1=1`,
|
||||
`T5=5`, `T10=10`, `T22=22`), toate `<21`, deci **`nIdTipDoc=5` (FACTURA) intotdeauna**, ceea ce prin
|
||||
`nIdTipDoc_Assign` face **`poDate.eProforma = 0` pe documentul nou, indiferent daca sursa era
|
||||
proforma**. Confirma direct pe cod ce raportul-sursa dedusese din trasare (`s5b_proforma_descarcare_gestiune.md` §3).
|
||||
|
||||
**2.4 Numar nou, intotdeauna.** `poGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)` (`:211`)
|
||||
ruleaza neconditionat, indiferent de copiere — copierea nu mosteneste numarul documentului sursa,
|
||||
alege intotdeauna un numar nou din seria tipului nou (`FACTURA`).
|
||||
|
||||
**2.5 Cursorul de linii candidate — intotdeauna `cursor_retur_document`.** `Do Case`-ul care alege
|
||||
cursorul Oracle de incarcat in `crsarticole` (`ofacturare.prg:266-308`) verifica **`Case m.llCopiere`
|
||||
primul**, inaintea oricarei ramuri pe `tnTip`:
|
||||
```
|
||||
Case m.llCopiere
|
||||
lcSqlCursor = [{call pack_facturare.cursor_retur_document(?poDate.in_valuta,?poDate.listaid,1,?poDate.eProforma,?gnIdUtil)}]
|
||||
```
|
||||
al treilea parametru pozitional `1` e `V_COPIERE`. Documentul sursa (proforma sau nu) intra prin
|
||||
`?poDate.listaid` (setat la `toDateAnterior.id_vanzare` in §2.3). `poDate.eProforma` trimis e cel al
|
||||
**documentului nou** (`0`, per §2.3), nu al sursei.
|
||||
|
||||
**2.6 Gestionabilitatea se restaureaza la copiere.** `cursor_retur_document`
|
||||
(`PACK_FACTURARE:3949-4000`) calculeaza `GESTIONABIL` cu exact aceasta expresie (`:3993-4000`):
|
||||
```sql
|
||||
(case
|
||||
when V_PROFORMA = 1 then 0
|
||||
when V_COPIERE = 1 then B.IN_STOC -- NOM_ARTICOLE.IN_STOC, articolul real
|
||||
else A.GESTIONABIL
|
||||
end) AS GESTIONABIL,
|
||||
```
|
||||
Cu `V_PROFORMA=0` (documentul nou nu e proforma) si `V_COPIERE=1`, ramura activa e
|
||||
`GESTIONABIL = B.IN_STOC` — valoarea reala din nomenclator, **indiferent daca documentul sursa era o
|
||||
proforma cu toate liniile fortate `gestionabil=0`**. Liniile copiate dintr-o proforma redevin
|
||||
gestionabile normal (daca articolul chiar e in stoc), trec prin `do_alege_stoc` la adaugare, primesc
|
||||
`id_gestiune` real, si descarca gestiune normal la emiterea facturii rezultate.
|
||||
|
||||
**2.7 Nimic auto-adaugat.** Comentariu explicit in cod (`ofacturare.prg:97-99`, in ramura
|
||||
`m.llCopiere` de mai jos in aceeasi functie):
|
||||
```
|
||||
* Nu adaug automat articolele din factura originala, ca sa dau posibilitate de modificare a cantitatilor, pretului, explicatiei
|
||||
* ofrmdetaliifactura.do_adauga_tot()
|
||||
```
|
||||
Liniile documentului sursa raman doar **candidate** in `crsarticole` (populat de `cursor_retur_document`
|
||||
la §2.5) — operatorul le adauga manual (sau cu "adauga tot"), la fel ca la orice document nou.
|
||||
|
||||
**2.8 Lista de preturi se adauga peste, doar la copiere.** Imediat dupa, tot in `factureaza()`
|
||||
(`ofacturare.prg:454-473`, citat integral in `docs\cercetare\s4e_lista_preturi_pe_sursa.md` §1):
|
||||
`pack_facturare.cursor_preturi(...)` intr-un cursor separat `crsArticoleTemp`, apoi
|
||||
`SELECT crsArticole / APPEND FROM DBF(lcCursorTemp)` — lipeste lista de preturi completa peste
|
||||
candidatii din documentul sursa, ca operatorul sa poata adauga si articole noi la copiere/modificare.
|
||||
Vezi sectiunea 6 pentru siguranta acestui pas.
|
||||
|
||||
---
|
||||
|
||||
## 3. Combo-ul `Ct_clb_fdoc` si realocarea de serie/numar la comutare
|
||||
|
||||
**Azi, combo-ul traieste exclusiv in dialogul separat `frm_date_factura`/`frm_date_aviz`**
|
||||
(clasa continuta in acelasi fisier text `ofacturare.vc2`, dar formular distinct de
|
||||
`frm_facturare_articole`/`frm_facturare_articole2`, care contin gridul de linii). Handler-ul e legat
|
||||
pe `LostFocus`, nu pe `InteractiveChange`:
|
||||
```
|
||||
PROCEDURE Ct_clb_fdoc._combobox1.LostFocus && ofacturare.vc2:9857-9859
|
||||
thisform.do_schimba_tipdoc()
|
||||
ENDPROC
|
||||
```
|
||||
`do_schimba_tipdoc` (`ofacturare.vc2:9396-9438`), pas cu pas:
|
||||
1. Determina `lnIdTipDoc` din textul combo-ului (`FACTURA`/`PROFORMA`/`BON FISCAL`/altfel `FACTURA`).
|
||||
2. Cheama neconditionat `poDate.initializeaza_setari_document(...)` cu un cod special (`-101` bon
|
||||
fiscal, `-102` proforma, sau `poDate.Tip` altfel) — realoca setarile de document (serie/numar)
|
||||
pentru noul tip, inainte sa stie daca tipul chiar s-a schimbat.
|
||||
3. **Iesire timpurie daca tipul nu s-a schimbat**: `IF poDate.nIdTipDoc = m.lnIdTipDoc THEN RETURN`.
|
||||
4. Daca s-a schimbat: `poDate.nract = 0`, `poDate.serie_act = ""` (curata selectia locala, nesalvata
|
||||
nicaieri inca), `poGeneratorNumere.dezaloca_numar(poDate.nIdTipDoc)` (**elibereaza in pool numarul
|
||||
vechi**, rezervat pe tipul vechi — nu se pierde, devine disponibil pentru alt document/alta sesiune;
|
||||
documentul nu are inca niciun numar scris in baza, fiind pre-Termina), apoi
|
||||
`poDate.nIdTipDoc = m.lnIdTipDoc` (declanseaza `nIdTipDoc_Assign`, care actualizeaza
|
||||
`eProforma`/`eBonFiscal`), si `poGeneratorNumere.creeaza_cursor_serii(poDate.nIdTipDoc)` reincarca
|
||||
seriile disponibile pentru noul tip, populand `clb_serie_act`.
|
||||
5. **Numarul vechi nu se "reia" automat** — utilizatorul trebuie sa aleaga din nou serie/numar pentru
|
||||
noul tip din controlul `clb_serie_act`, care s-a reincarcat la pasul 4.
|
||||
|
||||
**Ce NU atinge `do_schimba_tipdoc` azi**: `crsarticole`, `crsfactura`, `gestionabil`, `id_gestiune` —
|
||||
nimic legat de linii. **Motivul e structural, nu o omisiune**: azi comutarea e posibila **doar**
|
||||
inaintea oricarei linii, pentru ca `frm_date_factura` (unde traieste combo-ul) e un dialog modal
|
||||
care se inchide (`Release ofrmceredate`, `ofacturare.prg:248`) **inainte** ca Do Case-ul de incarcare
|
||||
a cursorului de linii sa ruleze (`:266`) — comutarea si incarcarea liniilor nu pot fi simultane azi.
|
||||
|
||||
**Ce se schimba cand combo-ul traieste in formularul unificat, cu gridul deja prezent**: exact
|
||||
premisa care dispare — combo-ul devine comutabil **si dupa** ce linii exista deja in `crsfactura`.
|
||||
`do_schimba_tipdoc` ramane corect pentru partea de serie/numar (pasii 1-5 de mai sus se aplica
|
||||
identic, indiferent daca gridul are linii sau nu — realocarea de serie e independenta de continutul
|
||||
documentului). **Ce lipseste e pasul 1.2/1.3 de mai sus (marcarea `gestionabil=0`/`id_gestiune=-1000`),
|
||||
care azi nu are nevoie sa fie legat de comutare pentru ca nu poate fi comutare cu linii deja
|
||||
prezente.** Vezi sectiunea 4.
|
||||
|
||||
---
|
||||
|
||||
## 4. Ce se rupe la unificare
|
||||
|
||||
**4.1 Marcarea negestionabil nu mai are un singur moment de executie.** Azi exista un singur loc
|
||||
(`ofacturare.prg:330-334`, dupa incarcarea cursorului, inaintea deschiderii gridului) unde
|
||||
`eProforma=1` implica `gestionabil=0` in masa. In formularul unificat, cu combo-ul viu in acelasi
|
||||
ecran cu gridul (decizia 10), sunt **trei momente distincte** care trebuie sa garanteze acelasi
|
||||
invariant, nu unul:
|
||||
a. **La deschiderea initiala**, daca formularul porneste direct pe tip Proforma (echivalent cu azi
|
||||
— se poate pastra `UPDATE` de masa pe cursorul candidat, daca inca exista un cursor de masa
|
||||
pentru sursa respectiva dupa S4/S4e; pe sursele deja mutate pe cautare filtrata/`APPEND BLANK`
|
||||
— S4e — nu mai exista cursor de masa de actualizat, marcarea trebuie facuta la construirea
|
||||
fiecarui rand).
|
||||
b. **La adaugarea unei linii noi cat timp `poDate.eProforma=1`** — deja identificat ca gol de
|
||||
proiectare in `s5b_proforma_descarcare_gestiune.md` §7 ("trebuie reimplementat linie-cu-linie,
|
||||
la momentul in care fiecare linie e adaugata"), confirmat aici ca valabil si pentru cazul in
|
||||
care operatorul a **pornit** documentul ca proforma inainte de a adauga prima linie (nu doar
|
||||
dupa un switch).
|
||||
c. **La comutarea combo-ului DUPA ce linii exista deja in `crsfactura`** (cazul explicit cerut de
|
||||
brief) — scenariu care azi nu poate exista, deci nu are cod de reutilizat. `do_schimba_tipdoc`
|
||||
trebuie extins (sau o metoda noua chemata din el) sa parcurga liniile deja prezente in
|
||||
`crsfactura` si sa aplice acelasi tratament ca la 1.2-1.3: `gestionabil=0`,
|
||||
`id_gestiune=-1000`, pe fiecare linie existenta, **cand comutarea intra pe Proforma**.
|
||||
*Corolarul necesar, netratat de nicio decizie/raport anterior*: pentru fiecare linie deja
|
||||
adaugata prin `do_alege_stoc` (gestionabila, cu `id_gestiune` real ales de operator), acea
|
||||
alegere de gestiune/lot devine irelevanta odata marcata negestionabil — de decis daca se
|
||||
**pastreaza tacit** valoarea veche (inofensiv, pentru ca `id_gestiune=-1000` o inlocuieste
|
||||
oricum in parametrul trimis la scriere) sau se **sterge explicit** din obiectul liniei, ca sa nu
|
||||
induca in eroare un ecran care ar afisa gestiunea aleasa pe o linie acum negestionabila.
|
||||
|
||||
**4.2 Combo-ul viu tot timpul inseamna ca `eProforma` poate flutura de mai multe ori inainte de
|
||||
Termina.** Azi tipul se alege o singura data, ireversibil (dialogul se inchide). In formularul
|
||||
unificat, operatorul poate comuta Factura -> Proforma -> Factura de mai multe ori inainte de a apasa
|
||||
`Termina`. Routing-ul de la Termina (sectiunea "Descoperire centrala") citeste `poDate.eProforma`
|
||||
**doar la momentul apasarii** — corect pentru starea finala — dar **starea liniilor** (marcate sau nu
|
||||
negestionabil, dupa istoricul comutarilor) nu se "reseteaza" singura la fiecare comutare, daca 4.1.c
|
||||
nu implementeaza si drumul invers. Vezi sectiunea 5.
|
||||
|
||||
**4.3 Realocarea de serie/numar (sectiunea 3) ramane corecta ca atare**, dar UX-ul ei (curatarea
|
||||
`clb_serie_act`, cererea catre operator sa aleaga din nou seria) trebuie sa functioneze si cu gridul
|
||||
de linii vizibil pe acelasi ecran — nu identificat niciun cod care sa presupuna ca gridul e ascuns in
|
||||
timpul realocarii; e o verificare vizuala, nu o problema de proiectare gasita in cod.
|
||||
|
||||
---
|
||||
|
||||
## 5. Drumul invers: proforma comutata inapoi in factura
|
||||
|
||||
**Acesta e riscul cel mai serios gasit in aceasta cercetare, nesemnalat explicit in rapoartele
|
||||
anterioare.**
|
||||
|
||||
Scenariu: operatorul porneste documentul ca Proforma (sau comuta pe Proforma la un moment dat),
|
||||
adauga linii — care, daca 4.1 e implementat corect, ajung marcate `gestionabil=0`/`id_gestiune=-1000`
|
||||
pe `crsfactura`. Apoi, **inainte de Termina**, comuta combo-ul inapoi pe Factura.
|
||||
|
||||
- **`poDate.eProforma` devine `0`** prin `nIdTipDoc_Assign`, corect.
|
||||
- **La Termina, routing-ul alege `scrie_factura2`/`scrie_factura_avize`** (sectiunea "Descoperire
|
||||
centrala") — care **chiar cheama** `contabilizeaza_articol` pentru fiecare linie.
|
||||
- **`contabilizeaza_articol` verifica exact sentinela `id_gestiune <> -1000`** (`PACK:7472-7476`)
|
||||
inainte de a chema `descarca_gestiune`. Liniile ramase marcate `-1000` de cand documentul era
|
||||
proforma **nu vor descarca gestiune**, desi documentul final e o factura reala, nu o proforma.
|
||||
- **Nu exista azi niciun mecanism care sa refaca `gestionabil`/`id_gestiune`** cand tipul revine la
|
||||
Factura in aceeasi sesiune. Singurul loc din tot codul citit unde gestionabilitatea se
|
||||
"restaureaza" e `cursor_retur_document` la **copiere** (`GESTIONABIL = B.IN_STOC` cand
|
||||
`V_COPIERE=1`, sectiunea 2.6) — mecanism legat de incarcarea unui cursor **nou**, la deschiderea
|
||||
unui document **nou**, nu de o comutare in acelasi document, in aceeasi sesiune, pe linii deja
|
||||
existente in `crsfactura`.
|
||||
|
||||
**Consecinta pe date**: o factura reala, emisa din formularul unificat dupa un du-te-vino prin
|
||||
Proforma, poate iesi din sesiune cu stocul nedescarcat pentru articole care ar fi trebuit sa-l
|
||||
descarce — silentios, fara eroare Oracle (sentinela `-1000` e o valoare valida pentru
|
||||
`contabilizeaza_articol`, nu declanseaza nicio exceptie).
|
||||
|
||||
**Ce trebuie proiectat, obligatoriu, pentru ca decizia 10 sa fie sigura**: la comutarea **dinspre**
|
||||
Proforma **catre** orice alt tip, liniile care au fost marcate negestionabil **exclusiv din cauza
|
||||
proformei** (nu articole real negestionabile in nomenclator) trebuie sa-si recapete
|
||||
`gestionabil`/`id_gestiune` reale, inainte de a ajunge la `Termina`. Doua variante, niciuna aleasa
|
||||
inca:
|
||||
a. **Re-interogare per linie** la comutare (analog cu `cursor_retur_document.GESTIONABIL`, dar
|
||||
aplicat pe liniile deja din `crsfactura`, nu la incarcarea unui cursor nou) — cere un apel
|
||||
Oracle (sau citire din nomenclator local, daca exista deja incarcat) per linie afectata, si
|
||||
re-deschiderea alegerii de gestiune/lot pentru cele gestionabile (echivalentul lui
|
||||
`do_alege_stoc`, care nu a rulat cand linia a fost adaugata sub Proforma).
|
||||
b. **Blocarea comutarii inapoi** cand exista deja linii adaugate sub Proforma — cere confirmare
|
||||
sau refuza tranzitia, obligand operatorul sa stearga liniile si sa le re-adauge sub noul tip.
|
||||
Mai simplu de implementat, cu cost UX (utilizatorul pierde munca de introducere).
|
||||
|
||||
**Nu e o decizie de proiectare pe care raportul o ia in locul lui Marius** — vezi sectiunea 11,
|
||||
punctul 1.
|
||||
|
||||
---
|
||||
|
||||
## 6. `id_c` la copiere — raspuns cu dovada
|
||||
|
||||
**Nu exista coliziune cu efect observabil pe drumul de copiere, azi.** Motivul, verificat direct pe
|
||||
cod (nu presupus, extras din `s4e_lista_preturi_pe_sursa.md` §1, reconfirmat aici pentru S5b):
|
||||
|
||||
`ofacturare.prg:454-473` face `APPEND FROM` peste `crsArticole`, lipind lista de preturi
|
||||
(`crsArticoleTemp`, numerotata `id_c` independent, cu `ROWNUM` propriu Oracle) peste continutul deja
|
||||
prezent (documentul copiat, incarcat la §2.5 din `cursor_retur_document`, cu propriul `id_c`
|
||||
independent). Cele doua seturi de `id_c` **se pot suprapune la nivel de valoare** — asta e adevarat,
|
||||
tehnic. Dar coliziunea **nu are efect**, pentru doua motive independente, ambele verificate:
|
||||
|
||||
1. **`Case m.llCopiere` e prima ramura verificata** in Do Case-ul de incarcare a cursorului
|
||||
(`ofacturare.prg:266-268`) — pentru orice document copiat, cursorul de baza e **intotdeauna**
|
||||
`cursor_retur_document`, indiferent de tipul original. Un document copiat nu (mai) e niciodata de
|
||||
tip `3` (comanda) dupa degradarea din `do_copiaza` (sectiunea 2.1: degradare catre
|
||||
`1,5,7,10,22,23` — **CORECTIE runda 13**, cifra veche `1,5,10,22` era gresita; setul real e citit
|
||||
din primul `CASE`, `ofacturare_comun.vc2:3693-3694`) —
|
||||
deci **nu poate ajunge** in ramura `Inlist(poDate.Tip,3,21,25,28,42,47)` a lui `do_scrie_factura`
|
||||
(`ofacturare.vc2:14332-14338`), care e singurul loc unde `id_c` conteaza pentru "cantitate
|
||||
ramasa" (Rol A, per `s4_punct2_registru_cantitate_ramasa.md`).
|
||||
2. **`do_sterge` potriveste pe `id_c`** (`ofacturare.vc2:14652-14655`), dar aceasta potrivire conteaza
|
||||
doar daca ceva citeste `crsarticole` ca registru dupa aceea — ceea ce nu se intampla pe tipurile
|
||||
rezultate din copiere (`1,5,7,10,22,23`, toate in ramura `Otherwise` a Do Case-ului de scriere, fara
|
||||
`Calculate Sum(cantitate)`).
|
||||
|
||||
**Concluzie, cu aceeasi rezerva ca in raportul-sursa**: coliziunea de `id_c` exista **tehnic** in
|
||||
datele din `crsArticole` dupa copiere, dar **nu are cale prin care sa produca un efect gresit**, atata
|
||||
timp cat tipul rezultat din copiere ramane in setul degradat (`1,5,7,10,22,23` — niciodata
|
||||
`3,21,25,28,42,47`).
|
||||
**Aceasta concluzie ramane valabila neschimbata si in formularul unificat**, pentru ca nu depinde de
|
||||
forma formularului — depinde doar de faptul ca `do_copiaza` degradeaza tipul inaintea oricarei
|
||||
incarcari de cursor, mecanism care nu se propune sa fie schimbat de S5b (sectiunea 7).
|
||||
|
||||
**O singura conditie de pastrat, explicit**: daca vreo poveste viitoare schimba `do_copiaza` sa NU
|
||||
mai degradeze tipul catre grupul "simplu" (de exemplu, sa permita copierea unei comenzi ca o comanda
|
||||
noua, nu ca factura din lista de preturi), concluzia de mai sus **trebuie re-verificata** — riscul de
|
||||
coliziune `id_c` descris in `s4e_lista_preturi_pe_sursa.md` verdict/punctul 1 ar deveni real.
|
||||
|
||||
---
|
||||
|
||||
## 7. Ce NU se atinge — lista explicita
|
||||
|
||||
Confirmate ca deja corecte si suficiente, fara nicio schimbare necesara pentru S5b:
|
||||
|
||||
1. **Sentinela `id_gestiune = -1000`** in `contabilizeaza_articol` (`PACK:7472-7476`) — ramane gate-ul
|
||||
de aparare pentru orice linie care ajunge totusi la contabilizare cu aceasta valoare (drumul
|
||||
invers, sectiunea 5, o foloseste ca sa NU descarce gestiune — ceea ce e exact problema de acolo,
|
||||
nu ceva de "reparat" in sentinela insasi).
|
||||
2. **Routing-ul `scrie_proforma` vs. `scrie_factura2`/`scrie_factura_avize`**
|
||||
(`ofacturare.vc2:14282-14300`) — corect prin constructie, citeste `poDate.eProforma` la momentul
|
||||
potrivit (Termina), nu are nevoie de nicio schimbare.
|
||||
3. **Cele cinci garzi `eProforma=0` pe atasamente** (`ofacturare.prg:1960,1996,2052,2063,2103`) —
|
||||
raman neschimbate, nu au legatura cu formularul (grid vs. dialog separat), doar cu momentul
|
||||
listarii/salvarii PDF, care ramane acelasi apel indiferent de forma UI.
|
||||
4. **Raportul propriu** (`PROFORMA`/`PROFORMA_VAL`, `ofacturare.prg:1638-1643`) — alegerea ramane pe
|
||||
`poDate.eProforma`, neschimbata.
|
||||
5. **`do_copiaza` (degradarea de tip)** — ramane exact cum e, decizia S5b (D) o cere explicit
|
||||
("degradarea de tip din `do_copiaza` ramane pentru copiere si nu se aplica la regenerare").
|
||||
6. **`copiere_factura` -> `factureaza(tip, toFactura)`** — punctul de intrare ramane neschimbat.
|
||||
7. **`completeaza_setari_document`** — comentariul de la `:370` (`nIdTipDoc` necopiat) e deliberat si
|
||||
ramane corect: documentul nou pleaca mereu ca Factura, indiferent de sursa, exact ce cere decizia
|
||||
S5b (copierea deschide "document nou", cu antetul editabil, nu proforma mostenita).
|
||||
8. **`cursor_retur_document` cu `V_COPIERE=1`** (`PACK:3949-4000`) — restaurarea `GESTIONABIL = B.IN_STOC`
|
||||
la copiere ramane corecta si suficienta pentru cazul "copiere", fara nicio schimbare (nu e acelasi
|
||||
mecanism cu comutarea in-sesiune de la sectiunea 5, care ramane un gol real).
|
||||
9. **Numarul alocat la copiere e intotdeauna nou** (`ofacturare.prg:208,211`, neconditionat de
|
||||
`llCopiere`) — nimic de schimbat.
|
||||
10. **Alocarea de serie/numar a lui `do_schimba_tipdoc`** (sectiunea 3) — mecanismul de dealocare +
|
||||
realocare ramane corect si reutilizabil ca atare in formularul unificat; doar absenta lui legata
|
||||
de linii (sectiunile 4-5) e golul de acoperit.
|
||||
|
||||
---
|
||||
|
||||
## 8. Pasi de implementare, ordonati
|
||||
|
||||
1. **Confirma pe date reale linia exacta unde `id_gestiune` devine `-1000`** pentru o linie de
|
||||
proforma (incertitudinea de la sectiunea 1.3) — headless, cu un `crsfactura` construit manual din
|
||||
`Scatter` pe o linie de proforma reala, inainte de orice alta schimbare de cod.
|
||||
*Gata cand:* se confirma cu certitudine (nu argument indirect) fie linia din
|
||||
`do_initializeaza_articol`, fie alt punct, unde proprietatea `id_gestiune` a liniei devine
|
||||
efectiv `-1000`.
|
||||
2. **Extrage marcarea "linie negestionabila pentru proforma" intr-o metoda proprie**, reutilizabila
|
||||
din trei locuri (sectiunea 4.1: incarcare initiala, adaugare linie noua, comutare combo pe linii
|
||||
existente) — un singur loc de intretinut pentru regula `eProforma=1 => gestionabil=0,
|
||||
id_gestiune=-1000`, nu trei copii.
|
||||
*Gata cand:* toate cele trei puncte de intrare cheama aceeasi metoda, verificat prin grep pe
|
||||
codul nou.
|
||||
3. **Leaga metoda de la pasul 2 de `do_schimba_tipdoc`**, pe ramura care duce spre Proforma, aplicata
|
||||
pe toate liniile deja prezente in `crsfactura` la momentul comutarii (sectiunea 4.1.c).
|
||||
*Gata cand:* pe un document cu linii deja adaugate ca Factura, comutarea combo-ului pe Proforma
|
||||
marcheaza `gestionabil=0`/`id_gestiune=-1000` pe fiecare linie existenta, verificabil prin citirea
|
||||
directa a `crsfactura` dupa comutare.
|
||||
4. **Proiecteaza si implementeaza drumul invers** (sectiunea 5) — varianta (a) sau (b), decizie a lui
|
||||
Marius (sectiunea 11, punctul 1). *Gata cand:* pe un document cu linii adaugate sub Proforma,
|
||||
comutat inapoi pe Factura inainte de Termina, fie liniile isi recapata `gestionabil`/`id_gestiune`
|
||||
reale (varianta a, verificabil prin `descarca_gestiune` apelat corect la emitere — vezi sectiunea
|
||||
9), fie comutarea inapoi e refuzata/necesita confirmare explicita (varianta b).
|
||||
5. **Verifica riscul de la sectiunea 4.1, corolarul**: decide daca `id_gestiune`/lotul ales anterior pe
|
||||
o linie acum negestionabila se pastreaza tacit sau se sterge din obiectul liniei — implementeaza
|
||||
alegerea si documenteaz-o in cod (un rand de comentariu, per conventia proiectului).
|
||||
6. **Regresie pe copiere**, fara nicio schimbare de cod asteptata (sectiunile 2, 6, 7) — pas de
|
||||
verificare, nu de implementare: confirma ca formularul unificat, rulat pe drumul de copiere, produce
|
||||
acelasi rezultat ca azi (sectiunea 9).
|
||||
7. **Regresie pe proforma simpla** (fara comutare, document pornit direct ca Proforma) — confirma ca
|
||||
pasii 2-3 nu au schimbat comportamentul cazului deja corect azi.
|
||||
|
||||
*Depinde de:* S5 (acoperirea tipurilor), S4/S4e (forma finala a incarcarii liniilor — pasul 2 trebuie
|
||||
sa stie daca opereaza pe un cursor de masa sau pe randuri individuale, dupa ce alte povesti decid
|
||||
asta pentru fiecare sursa).
|
||||
|
||||
---
|
||||
|
||||
## 9. Cum se verifica
|
||||
|
||||
**Proba ceruta de plan**: o proforma emisa din formularul unificat nu produce nota contabila si se
|
||||
listeaza pe raportul ei; o copie produce un document nou cu numar nou si acelasi continut.
|
||||
|
||||
**Interogari SQL concrete** (numai `SELECT`, rulate cu unealta PowerShell + `sqlplus.exe`, per
|
||||
mediul de proiect):
|
||||
|
||||
```sql
|
||||
-- 1. Proforma emisa din formularul unificat: fara nota contabila, fara descarcare de gestiune
|
||||
SELECT id_vanzare, tip, eproforma, sters FROM vanzari WHERE id_vanzare = :ID_TEST;
|
||||
-- eproforma trebuie sa fie 1
|
||||
|
||||
SELECT COUNT(*) FROM vanzari_detalii WHERE id_vanzare = :ID_TEST;
|
||||
-- trebuie sa coincida cu numarul de linii adaugate (liniile SE scriu, doar nu se contabilizeaza)
|
||||
|
||||
SELECT id_vanzare_det, id_articol, id_gestiune FROM vanzari_detalii WHERE id_vanzare = :ID_TEST;
|
||||
-- id_gestiune trebuie sa fie NULL pe fiecare linie (sentinela -1000 tradusa de adauga_articol_factura)
|
||||
|
||||
SELECT COUNT(*) FROM note_contabile nc
|
||||
JOIN act a ON a.id_fact = nc.id_fact -- sau echivalentul legaturii act/nota folosite in schema
|
||||
WHERE a.cod = (SELECT cod FROM vanzari WHERE id_vanzare = :ID_TEST);
|
||||
-- trebuie sa fie 0 -- nicio nota contabila generata
|
||||
|
||||
-- 2. Fara descarcare de gestiune: RUL nu are miscare noua dupa emiterea proformei
|
||||
SELECT COUNT(*) FROM rul WHERE dataora >= :MOMENT_EMITERE AND id_articol IN (:articolele_testate);
|
||||
-- trebuie sa fie identic cu inainte de emitere (0 randuri noi legate de acest document)
|
||||
|
||||
-- 3. Copie: document nou, numar nou, acelasi continut
|
||||
SELECT id_vanzare, numar_act, serie_act, eproforma FROM vanzari WHERE id_vanzare = :ID_COPIE;
|
||||
-- eproforma = 0; numar_act/serie_act diferite de documentul sursa
|
||||
|
||||
SELECT vd.id_articol, vd.cantitate, vd.pret, vd.id_gestiune
|
||||
FROM vanzari_detalii vd WHERE vd.id_vanzare = :ID_COPIE
|
||||
ORDER BY vd.id_articol;
|
||||
-- comparat linie cu linie cu vanzari_detalii al documentului sursa (:ID_TEST) -- acelasi articol/cantitate/pret;
|
||||
-- id_gestiune insa REAL (nu NULL), daca articolul e gestionabil -- confirma restaurarea de la sectiunea 2.6
|
||||
|
||||
-- 4. Drumul invers (sectiunea 5, dupa ce varianta e implementata): factura emisa dupa un du-te-vino prin Proforma
|
||||
-- descarca gestiune normal, ca orice factura
|
||||
SELECT id_vanzare, eproforma FROM vanzari WHERE id_vanzare = :ID_TEST_INVERS;
|
||||
-- eproforma = 0
|
||||
SELECT id_gestiune FROM vanzari_detalii WHERE id_vanzare = :ID_TEST_INVERS;
|
||||
-- NU mai e NULL pe liniile gestionabile -- confirma ca varianta aleasa la pasul 4 (sectiunea 8) functioneaza
|
||||
SELECT COUNT(*) FROM rul WHERE dataora >= :MOMENT_EMITERE_INVERS AND id_articol IN (:articolele_testate);
|
||||
-- trebuie sa arate miscare noua -- gestiunea s-a descarcat
|
||||
```
|
||||
|
||||
**Protocol**: fiecare interogare rulata **inainte si dupa** implementare, pe date de test resetate
|
||||
identic, per memoria de proiect "zero cazuri in date nu e dovada" — minim un caz per scenariu descris
|
||||
mai sus (proforma simpla, proforma cu switch la comutare, copiere din proforma, drumul invers),
|
||||
nu doar "a mers o data".
|
||||
|
||||
---
|
||||
|
||||
## 10. Ce nu se poate testa headless
|
||||
|
||||
- **Comutarea combo-ului `Ct_clb_fdoc` cu gridul de linii vizibil in acelasi ecran** — capcana deja
|
||||
confirmata pe acest proiect (`grid-coloane-nu-se-materializeaza-headless`, memorie de proiect):
|
||||
sub `-A -T`, `ColumnCount`/`RecordSource` raman artefacte, comportamentul real al `LostFocus`
|
||||
pe combo si al reincarcarii `clb_serie_act` cere harnessul UI vizibil.
|
||||
- **Dialogul `frm_articol_factura` vs. `do_alege_stoc`** (sectiunea 1.3, decizia de dialog dupa
|
||||
`gestionabil`) — depinde de randare de formular modal, nu apelabil direct fara UI.
|
||||
- **Verificarea vizuala ca liniile deja adaugate isi schimba starea la comutare** (sectiunea 4.1.c) —
|
||||
daca implementarea alege sa arate un indicator vizual (de exemplu, o coloana/culoare care marcheaza
|
||||
"negestionabil"), acel indicator nu se verifica headless; **continutul** `crsfactura` (valorile
|
||||
`gestionabil`/`id_gestiune`) **se poate** verifica headless, cu apeluri directe la metoda noua din
|
||||
pasul 2 (sectiunea 8), fara sa deschida formularul.
|
||||
- **Realocarea vizuala a seriei/numarului in `clb_serie_act`** — control de UI, comportamentul lui de
|
||||
reincarcare (`do_initializeaza`) nu se verifica fara randare.
|
||||
- **Ce se poate verifica headless, direct**: continutul `crsfactura`/`poDate` dupa apeluri directe
|
||||
(nu prin click) la metoda de marcare (pasul 2), la `do_schimba_tipdoc`, si la rutina drumului
|
||||
invers (pasul 4) — cu date de test pregatite manual (un `crsfactura` cu 2-3 linii, `poDate.eProforma`
|
||||
comutat programatic inainte si dupa) — confirma `gestionabil`, `id_gestiune`, `poDate.eProforma`,
|
||||
fara sa deschida formularul. La fel, toate interogarile SQL din sectiunea 9 sunt verificabile
|
||||
headless (SQL direct, fara UI).
|
||||
|
||||
---
|
||||
|
||||
## 11. Riscuri si decizii ramase lui Marius
|
||||
|
||||
1. **Drumul invers (sectiunea 5) — varianta (a) sau (b).** Cel mai important punct deschis al acestui
|
||||
raport: fara o decizie explicita aici, decizia 10 din plan ("proforma foloseste acelasi formular")
|
||||
ramane incompleta — un document care trece prin Proforma si revine la Factura in aceeasi sesiune
|
||||
poate iesi cu stocul nedescarcat, silentios. **Recomandare**: varianta (a) (re-interogare si
|
||||
re-deschidere a alegerii de gestiune la comutarea inapoi), pentru ca varianta (b) (blocarea
|
||||
comutarii) contrazice explicit decizia 10 ("combo-ul ramane in antetul unificat", implicit
|
||||
liber de folosit in ambele sensuri) si ar surprinde operatorul cu o pierdere de munca fara
|
||||
avertisment prealabil la momentul comutarii spre Proforma.
|
||||
2. **Ce se intampla cu `id_gestiune`/lotul ales anterior pe o linie marcata ulterior negestionabil**
|
||||
(sectiunea 4.1, corolarul) — pastrare tacita (mai simplu, fara cod nou) sau stergere explicita
|
||||
(mai clar pentru un ecran viitor care ar afisa gestiunea). **Recomandare**: pastrare tacita — nu
|
||||
ajunge niciodata la server oricum (sentinela `-1000` trimisa explicit ignora orice valoare veche),
|
||||
iar stergerea ar cere cod suplimentar fara beneficiu functional, doar cosmetic.
|
||||
3. **Incertitudinea ramasa din raportul-sursa** (sectiunea 1.3: linia exacta unde `id_gestiune` devine
|
||||
`-1000`) — necesara inainte de a scrie codul pasului 2 (sectiunea 8), nu doar o curiozitate.
|
||||
Nu e o decizie de produs, e o verificare tehnica obligatorie inaintea implementarii.
|
||||
4. **Indicator vizual pentru liniile marcate negestionabil la comutare** (mentionat la sectiunea 10) —
|
||||
optional, nu cerut explicit de plan; de decis daca merita un semnal in grid (culoare/iconita) sau
|
||||
ramane invizibil pana la emitere. Nu blocheaza nimic, dar afecteaza UX-ul comutarii repetate.
|
||||
5. **Testarea pe date reale a `FACT-007`** (mentionata in raportul-sursa ca argument indirect,
|
||||
nu dovada) — daca Marius vrea certitudine completa inainte de implementare, un test manual pe
|
||||
mediu de dezvoltare (proforma cu articol gestionabil din comanda, verificare directa
|
||||
`VANZARI_DETALII.ID_GESTIUNE IS NULL`) inchide definitiv acest gol, in afara perimetrului
|
||||
read-only al acestei cercetari.
|
||||
|
||||
---
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare + proiectare incheiate intr-o singura sesiune, fara sa fie nevoie de predare de context.
|
||||
Toate cele 11 sectiuni cerute de brief sunt complete, cu `fisier:linie` verificat direct pe fisierele
|
||||
text reale (`ofacturare.vc2`, `ofacturare_comun.vc2`, `ofacturare.prg`, `ofacturare_comun.prg` — nu
|
||||
`.bak`) si pe corpul `PACK_FACTURARE` de pe `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`.
|
||||
|
||||
**Descoperirea centrala** (sectiunea "Descoperire centrala"): mecanismul care blocheaza nota
|
||||
contabila pe proforma nu e (doar) sentinela `-1000` din `contabilizeaza_articol`, ci alegerea
|
||||
`scrie_proforma` (fara contabilizare deloc) vs. `scrie_factura2`/`scrie_factura_avize` (cu
|
||||
contabilizare) in `do_scrie_factura`, facuta dupa `poDate.eProforma` la Termina. Aceasta descoperire
|
||||
schimba unde trebuie sa se concentreze grija de proiectare: nu pe "cum ajunge `-1000` la server"
|
||||
(deja acoperit corect de codul existent), ci pe **ce se intampla cu liniile deja marcate `-1000` cand
|
||||
documentul nu mai e proforma la Termina** — exact riscul din sectiunea 5, singurul loc unde sentinela
|
||||
chiar conteaza si azi nu exista nicio plasa.
|
||||
|
||||
Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, niciun commit, nicio scriere pe
|
||||
Oracle (doar `SELECT`/`Read`/`Grep` pe fisiere de pe disc).
|
||||
374
docs/cercetare/s5c_factura_din_proforma.md
Normal file
374
docs/cercetare/s5c_factura_din_proforma.md
Normal file
@@ -0,0 +1,374 @@
|
||||
# S5C — Factura din proforma (proiectare)
|
||||
|
||||
Status: INCHEIAT
|
||||
|
||||
## Sarcina
|
||||
Cerinta noua (decizia 43, runda 12): dintr-o proforma emisa sa se poata genera o
|
||||
FACTURA ca document NOU (nu prin comutarea tipului pe acelasi document — comutarea
|
||||
PROFORMA -> FACTURA cu linii e deja blocata cu mesaj). Mecanismul pare sa existe deja
|
||||
pe calea de copiere (`do_copiaza`).
|
||||
|
||||
## (a) Poate fi azi o proforma aleasa ca sursa de copiere?
|
||||
|
||||
**Da, fara nicio excludere.** Trei dovezi convergente:
|
||||
|
||||
1. `COMUN\clase\ofacturare_comun.vc2:4964-4974` — `frm_facturi.IsCopy(tnTip)`:
|
||||
```
|
||||
RETURN .T. && POT SA COPIEZ ORICE, TRATEZ TIPURILE IN DO_COPIAZA
|
||||
*!* RETURN INLIST(m.lnTip, 1,5,7,10,22,23,43) && pot sa copii doar avize si facturi pret de lista, ...
|
||||
```
|
||||
Varianta veche (restrictiva, comentata) verifica doar `tip` (1-52) — niciodata `eproforma`. Varianta
|
||||
activa returneaza necondiționat `.T.`. Nu exista, si n-a existat vreodata in acest cod, o excludere
|
||||
pe `eproforma`.
|
||||
2. `COMUN\clase\ofacturare_comun.vc2:5110` — vizibilitatea butonului de copiere pe randul selectat:
|
||||
`Thisform.but_copiaza1.Visible = Thisform.IsCopy(crsFacturi.tip)` — acelasi apel, acelasi rezultat
|
||||
necondiționat.
|
||||
3. `COMUN\clase\ofacturare_comun.vc2:5082-5083` — gridul de facturi are un filtru dedicat pe
|
||||
`eproforma`, cu proforma tratata ca subset normal, nu ascuns:
|
||||
```
|
||||
"Facturi&Avize\nofiled\E\(eproforma = 0)\" + crlf + ;
|
||||
"Proforme\nofiled\E\(eproforma=1)\" + crlf + ;
|
||||
```
|
||||
Chiar filtrul "Proforme" arata explicit ca proformele sunt navigabile si selectabile normal in
|
||||
acelasi grid — butonul de copiere ramane vizibil identic pe orice rand selectat din acest filtru.
|
||||
|
||||
**Concluzie**: azi orice utilizator poate selecta o proforma in grid si apasa "Copiere (CTRL+K)"
|
||||
(tooltip-ul insusi anunta explicit acest caz de folosire — vezi citatul din
|
||||
`s5b_proiectare_proforma_copiere.md` §"1.4"/`proforma_copiere_puncte_intrare.md:41-47`). Nu exista
|
||||
nicio garda de blocat, la niciun nivel (`IsCopy`, vizibilitate buton, filtru grid).
|
||||
|
||||
## (b) Ce tip rezulta din copierea unei proforme?
|
||||
|
||||
**Rezultatul e intotdeauna `nIdTipDoc=5` (FACTURA)** — corect pentru o factura fiscala — dar tipul de
|
||||
business (`VANZARI.TIP`, 1-52) degradeaza dupa tabelul deja existent din `do_copiaza`, **nemodificat
|
||||
pentru cazul proforma**: nu exista nicio ramura speciala pe `eproforma` in `do_copiaza`
|
||||
(`COMUN\clase\ofacturare_comun.vc2:3628-3713`) — degradarea se face **strict dupa `loFactura.tip`**,
|
||||
indiferent daca documentul sursa era proforma sau nu.
|
||||
|
||||
**Pasul 1 — degradarea de tip business** (`:3693-3708`, tabel deja confirmat in
|
||||
`s5b_proiectare_proforma_copiere.md` §2.1):
|
||||
- Proforma poate exista doar pe tipuri "de factura" (combo-ul `Ct_clb_fdoc` traieste in
|
||||
`frm_date_factura`, nu si in `frm_date_aviz` — cf. `proforma_copiere_puncte_intrare.md` §1). Setul
|
||||
posibil de `loFactura.tip` pentru o proforma e deci printre `{1,2,3,4,5,6,7,8,9,10,43,44,45,47,48,49,51}`
|
||||
(grupul "Facturi" din `COMUN\docs\tipuri_documente_facturare.md`).
|
||||
- Din acest set: `{1,5,7,10}` raman neschimbate (`CASE INLIST(loFactura.tip,T1,T5,T7,T10,T22,T23)`,
|
||||
`:3694`); `{2,3,4,8,43,44,45,47,48,49,51}` degradeaza la `T1` (`:3696-3697`); `{6}` la `T10`
|
||||
(`:3698-3699`); `{9}` la `T5` (`:3700-3701`).
|
||||
- Deci, pentru orice proforma emisa astazi (indiferent de tipul de business original), documentul
|
||||
copiat va avea `TIP` in `{1,5,7,10}` — nucleul "lista de preturi" (lei/valuta) sau credit note.
|
||||
|
||||
**Pasul 2 — `nIdTipDoc` NU se propaga** (confirmat deja in
|
||||
`s5b_proiectare_proforma_copiere.md` §2.3, `COMUN\programe\ofacturare_comun.prg:370`:
|
||||
`*.nIdTipDoc = toDateAnterior.nIdTipDoc` — comentata). Documentul nou primeste `nIdTipDoc` implicit
|
||||
dupa tipul degradat: `Do Case` la `COMUN\programe\ofacturare.prg:187-196`, toate valorile din
|
||||
`{1,5,7,10}` cad sub `5` (FACTURA) → `poDate.eProforma = 0` pe documentul nou, **indiferent ca sursa
|
||||
era proforma**.
|
||||
|
||||
**Concluzie pentru (b)**: mecanismul de copiere transforma azi orice proforma intr-o **FACTURA reala
|
||||
(`nIdTipDoc=5`, `eproforma=0`)**, de tip business `1` (lista de preturi, cel mai frecvent caz — orice
|
||||
tip din `{2,3,4,8,43,44,45,47,48,49,51}` cade tot pe `T1`), `5`/`10` (daca sursa era deja pe lista de
|
||||
preturi in valuta/factura valuta), sau `7` (credit note, ramas neschimbat). Tipul rezultat **e cel bun
|
||||
pentru o factura fiscala** din perspectiva `nIdTipDoc`/`eproforma` (nu ramane proforma), dar tipul de
|
||||
business e intotdeauna **degradat catre "lista de preturi"** — proforma emisa dintr-o comanda (`tip=3`)
|
||||
sau dintr-un aviz (`tip=4`) devine, dupa copiere, o factura "banala" `tip=1`, **la fel ca la orice alta
|
||||
copiere non-proforma** (comportament deja documentat si acceptat in S5b §7 punctul 5: "degradarea de
|
||||
tip din `do_copiaza` ramane pentru copiere si nu se aplica la regenerare" — nicio schimbare ceruta aici
|
||||
fata de acel raport).
|
||||
|
||||
## (c) Legatura proforma -> factura (VANZARI_CORESP.TIP)
|
||||
|
||||
### Structura tabelei (confirmata pe schema vie, `MARIUSM_AUTO`)
|
||||
```
|
||||
ID_VANZARE_CORESP NUMBER NOT NULL (PK, populat de trigger BEFORE INSERT din SEQ_VANZARI_CORESP)
|
||||
ID_VANZARE_FACT NUMBER NOT NULL
|
||||
ID_VANZARE_AVIZ NUMBER NOT NULL (nume generic mostenit — nu neaparat un aviz, vezi mai jos)
|
||||
STERS NUMBER NULL
|
||||
TIP NUMBER NOT NULL
|
||||
```
|
||||
Singurul trigger gasit pe tabela (`TRG_VANZARI_CORESP_BEFOINS`, prezent identic in schemele `ACN` si
|
||||
`MARIUSM_AUTO`) doar genereaza PK-ul din secventa — nu atinge/valideaza `TIP`.
|
||||
|
||||
### Cine scrie in tabela — un singur punct, in tot codul Oracle
|
||||
`grep` pe `all_source` (schema vie) dupa `VANZARI_CORESP` gaseste **un singur writer**:
|
||||
`pack_facturare.scrie_corespondente_vanzari` (`PACK_FACTURARE:15481-15516`, singurul `INSERT INTO
|
||||
VANZARI_CORESP` din toata baza). Nu exista alt pachet, trigger sau produs ROA (ROAGEST, ROAIMOB,
|
||||
ROACONTRACTE, ROAACNPRO etc.) care sa scrie in aceasta tabela — `PACK_FACTURARE` traieste in `COMUN`,
|
||||
partajat de toata suita, deci **orice** consumator ar trece prin acelasi punct.
|
||||
|
||||
### Valorile de `TIP` deja folosite — confirmate cod + date
|
||||
**Cod** (singurele 3 apeluri la `scrie_corespondente_vanzari` din tot `PACK_FACTURARE`,
|
||||
`:14818-14839`, in `finalizeaza_factura`):
|
||||
- `TIP=1`: `WHEN pack_facturare.ntip = 4 THEN ... scrie_corespondente_vanzari(1)` — factura scrisa
|
||||
dintr-un aviz (`ntip=4` = "FACT. DIN AVIZ"). Foloseste `pack_facturare.clistaid_avize` (lista
|
||||
separata, populata pentru facturare-din-aviz cu selectie multipla).
|
||||
- `TIP=2`: `WHEN pack_facturare.ntip = 24 THEN ... scrie_corespondente_vanzari(2)` — aviz de retur
|
||||
(`ntip=24`).
|
||||
- `TIP=3`: `WHEN pack_facturare.ntip IN (8, 9) THEN ... scrie_corespondente_vanzari(3)` — factura de
|
||||
retur.
|
||||
Toate trei folosesc `pack_facturare.nid_vanzare` (documentul curent, tocmai scris) ca
|
||||
`ID_VANZARE_FACT`; pentru `TIP=1` sursa vine din `clistaid_avize`, pentru `TIP=2`/`TIP=3` (ramura
|
||||
`ELSE` din `scrie_corespondente_vanzari`, `:15490-15491`) din `pack_facturare.clistaid` generic.
|
||||
|
||||
**Date** (schema `MARIUSM_AUTO`, interogare directa `SELECT tip, COUNT(*) FROM vanzari_coresp GROUP BY
|
||||
tip`): `TIP=1` → 6 randuri, `TIP=2` → 1, `TIP=3` → 1. **Zero randuri cu alt `TIP`.** Coerent cu codul
|
||||
(nu exista alt loc care sa scrie alte valori) — dovada dubla (cod + date), nu doar una din ele (per
|
||||
memoria de proiect "zero cazuri in date nu e dovada": aici avem *si* absenta din date, *si* absenta
|
||||
structurala in cod, ceea ce e o dovada tare, nu doar un data point izolat).
|
||||
|
||||
### Valoare noua libera
|
||||
**`TIP=4` e liber**, confirmat pe ambele fronturi (cod: niciun apel existent la
|
||||
`scrie_corespondente_vanzari(4)`; date: zero randuri `TIP=4` in schema testata). Recomandare: **`TIP=4`
|
||||
= "factura scrisa dintr-o proforma"**, urmatorul numar disponibil in secventa deja folosita (1,2,3),
|
||||
fara conflict cu niciun consumator existent (nu exista alt produs ROA care sa scrie sau sa citeasca
|
||||
`VANZARI_CORESP` in afara de `PACK_FACTURARE`). Structura tabelei **nu se schimba** — doar o noua
|
||||
valoare de enum in coloana `TIP`, exact cum a cerut misiunea.
|
||||
|
||||
### Unde s-ar scrie corespondenta — punct de intrare, cu rezerva importanta
|
||||
Tiparul de azi (`TIP=1/2/3`) scrie corespondenta **din interiorul** `finalizeaza_factura`, intr-un
|
||||
`CASE` cheie **`pack_facturare.ntip`** — semnalul de business-type al documentului nou scris. Aceasta
|
||||
cheie **nu poate distinge** "factura rezultata dintr-o copiere de proforma" de "orice alta factura
|
||||
obisnuita cu acelasi tip degradat" (vezi (b): rezultatul copierii unei proforme cade intotdeauna in
|
||||
`{1,5,7,10}`, niciodata `4`/`24`/`8`/`9` — deci nu exista azi nicio ramura `WHEN` pe care s-o extinda,
|
||||
si niciuna nu s-ar putea scrie corect doar din `ntip`, pentru ca acelasi `ntip=1` rezulta si dintr-o
|
||||
copiere obisnuita de factura, nelegata de nicio proforma). Vezi Capcana (i) pentru raspunsul complet la
|
||||
"de unde stie Oracle ca sursa a fost o proforma" si propunerea de proiectare pentru rezolvare.
|
||||
|
||||
## Capcana (i): supravietuieste id_fact/id_vanzare al sursei pana la scrierea corespondentei?
|
||||
|
||||
**Da, `id_vanzare` al proformei supravietuieste — dovedit pas cu pas — dar faptul ca sursa "era o
|
||||
proforma" (nu doar "era un document oarecare") NU supravietuieste nicaieri azi. Asta e golul real.**
|
||||
|
||||
**Partea care functioneaza deja, neschimbata:**
|
||||
1. `do_copiaza` (`COMUN\clase\ofacturare_comun.vc2:3690-3691`): `SELECT crsFacturi / SCATTER NAME
|
||||
loFactura MEMO` — `loFactura` e o copie completa a randului sursa din grid, **inclusiv
|
||||
`loFactura.id_vanzare`** (campul cheie) si `loFactura.eproforma` (coloana de grid confirmata la
|
||||
`ofacturare_comun.vc2:2494`, `Column37.ControlSource = "eproforma"`) — **ambele disponibile in
|
||||
acest moment**, inainte ca degradarea de tip (`:3693-3708`) sa ruleze.
|
||||
2. `copiere_factura` (`COMUN\programe\oproceduri_facturare.prg:150-153`) → `factureaza(loFactura.tip,
|
||||
loFactura)` — `loFactura` intreg (cu `id_vanzare` si `eproforma` inca pe el) devine `toFactura`,
|
||||
parametrul opțional.
|
||||
3. `completeaza_setari_document(toDateAnterior, .T.)`
|
||||
(`COMUN\programe\ofacturare_comun.prg:362-412`), ramura de copiere: **`.listaid =
|
||||
toDateAnterior.id_vanzare`** (`:387`) — **aici** `id_vanzare` al proformei ajunge in `poDate`,
|
||||
proprietate care **nu e atinsa de degradarea de tip** (aceea opereaza doar pe `loFactura.tip`,
|
||||
variabila locala din `do_copiaza`, complet separata de `poDate`).
|
||||
4. `poDate.listaid` calatoreste neschimbat prin toata sesiunea (folosit si la §2.5 din
|
||||
`s5b_proiectare_proforma_copiere.md` pentru `cursor_retur_document`), pana la scriere:
|
||||
`do_scrie_articole` (`COMUN\clase\ofacturare.vc2:6104`): `Alltrim(Nvl(poDate.listaid,''))` e trimis
|
||||
ca parametru `V_LISTAID` catre `pack_facturare.initializeaza_date_factura`, care il pune in
|
||||
`pack_facturare.clistaid := V_LISTAID` (`PACK_FACTURARE:1883`) — **inainte** ca `do_scrie_factura`
|
||||
sa aleaga `scrie_factura2`/`finalizeaza_factura`. La momentul in care `scrie_corespondente_vanzari`
|
||||
ar rula, `pack_facturare.clistaid` **este deja** `id_vanzare`-ul proformei (ca text), exact valoarea
|
||||
pe care ramura `ELSE` a lui `scrie_corespondente_vanzari` (`:15490-15491`, folosita azi de `TIP=2` si
|
||||
`TIP=3`) o citeste.
|
||||
|
||||
**Partea care NU exista azi — golul real:** `completeaza_setari_document` (pasul 3 de mai sus) copiaza
|
||||
explicit `id_lucrare`, `nrord`, `id_sectie`, `sectie`, `id_agent`, `nume_agent`, `id_delegat`,
|
||||
`nume_delegat`, `BIdelegat`, `CNPdelegat`, `nrinmat`, `id_masina`, `listaid`, `descriere`, `id_client`,
|
||||
`nume_client` de pe `toDateAnterior` — **dar niciodata `.eproforma`**. Documentul nou stie "din ce
|
||||
`id_vanzare` a fost copiat" (`.listaid`), dar **nu stie daca acel document sursa era o proforma sau o
|
||||
factura obisnuita** — informatia se pierde exact la acest pas, inainte sa ajunga la Oracle.
|
||||
|
||||
**De ce conteaza**: `finalizeaza_factura` (Oracle) decide azi ce `TIP` de corespondenta sa scrie
|
||||
uitandu-se **doar** la `pack_facturare.ntip` (business-type-ul documentului nou). Cum am stabilit la
|
||||
(b), rezultatul unei copieri de proforma cade intotdeauna in `{1,5,7,10}` — **acelasi interval** in
|
||||
care cade si o copiere obisnuita (proforma sau nu). Oracle **nu poate reconstitui** din `ntip` singur
|
||||
daca documentul curent a fost copiat dintr-o proforma sau dintr-o factura normala — nu exista niciun
|
||||
semnal in `pack_facturare` care sa poarte aceasta distinctie.
|
||||
|
||||
**Ce trebuie adaugat, minimal** (propunere, nu implementare):
|
||||
1. **VFP**: la `completeaza_setari_document:387`, langa `.listaid = toDateAnterior.id_vanzare`, adauga
|
||||
o proprietate noua, de exemplu `.lProformaSursa = (toDateAnterior.eproforma = 1)` — un simplu boolean
|
||||
client-side, capturat in acelasi moment si din acelasi obiect sursa unde `listaid` e deja capturat
|
||||
(zero risc suplimentar de "se pierde intre timp", pentru ca foloseste exact acelasi tipar dovedit la
|
||||
pasul 3-4 de mai sus).
|
||||
2. **Scrierea corespondentei, NU prin `finalizeaza_factura`/`ntip`**: pentru ca `ntip` nu poate purta
|
||||
distinctia (vezi mai sus), cea mai sigura ruta e un apel Oracle **explicit, separat**, facut din VFP
|
||||
imediat dupa ce `do_scrie_factura` confirma succesul scrierii documentului nou, gardat de
|
||||
`poDate.lProformaSursa`:
|
||||
```
|
||||
IF poDate.lCopiere AND poDate.lProformaSursa
|
||||
lcSql = [{call pack_facturare.scrie_corespondente_vanzari(4)}]
|
||||
* ... goExecutor.oExecute(lcSql) ...
|
||||
ENDIF
|
||||
```
|
||||
Aceasta reutilizeaza **exact** procedura Oracle existenta, neschimbata (ramura `ELSE`,
|
||||
`pack_facturare.clistaid`/`pack_facturare.nid_vanzare` inca valide in sesiune la acel moment, per
|
||||
pasul 4 de mai sus) — **zero cod PL/SQL nou**, doar un nou punct de apel VFP si o noua valoare de
|
||||
`TIP`. Alternativa (adaugarea unei ramuri noi in `CASE`-ul din `finalizeaza_factura`, cheie pe un
|
||||
parametru nou trimis prin `V_PARAMETRU_ADITIONAL` sau similar) ar fi mai fragila: acel `CASE` e
|
||||
punctul comun al **oricarei** facturi/aviz/retur scrise in tot codul, folosit de toate produsele ROA
|
||||
prin `PACK_FACTURARE` partajat — o ramura noua acolo, keyed pe o combinatie de `ntip` + un flag nou,
|
||||
ar creste suprafata unui switch deja incarcat, pentru un caz care oricum nu poate fi exprimat corect
|
||||
doar din `ntip`. Apelul explicit izolat e mai simplu si nu atinge deloc `finalizeaza_factura`.
|
||||
3. **Ordinea conteaza**: apelul explicit trebuie sa ruleze **inainte** ca orice alt cod Oracle din
|
||||
aceeasi sesiune sa resetteze `pack_facturare.nid_vanzare`/`clistaid` (de exemplu, o alta scriere de
|
||||
document in acelasi batch) — de plasat imediat dupa `do_scrie_factura`, inainte de listare/atasamente
|
||||
(care oricum nu ating Oracle pentru partea de scriere). Daca se prefera robustete completa (fara
|
||||
dependenta de stare de sesiune Oracle), varianta alternativa e sa se paseze explicit
|
||||
`V_ID_VANZARE_FACT` (= `poDate.id_vanzare`, cunoscut client-side dupa scriere) si
|
||||
`V_ID_VANZARE_PROFORMA` (= `poDate.listaid`, deja cunoscut) direct ca parametri, printr-o mica
|
||||
procedura Oracle noua care ocoleste `clistaid`/`nid_vanzare` cu totul — cost: un nou obiect PL/SQL in
|
||||
loc de zero, beneficiu: independenta de ordinea altor apeluri din sesiune. **Alegere ramasa lui
|
||||
Marius** (vezi propunerea finala).
|
||||
|
||||
## Capcana (ii): `marcheaza_facturat` pe proforma — corect sau nu?
|
||||
|
||||
**Nu trebuie chemat pe proforma. Confirmat cu dovada, pe patru argumente convergente.**
|
||||
|
||||
**Ce face, exact** (`PACK_FACTURARE:15381-15418`):
|
||||
```sql
|
||||
PROCEDURE marcheaza_facturat(V_VERIFICARE IN NUMBER) IS
|
||||
BEGIN
|
||||
IF V_VERIFICARE = 0 THEN
|
||||
UPDATE VANZARI SET FACTURAT = 1, ID_UTILFACT = pack_facturare.nid_util
|
||||
WHERE ID_VANZARE IN (SELECT id_vanzare_aviz FROM vanzari_coresp
|
||||
WHERE id_vanzare_Fact = pack_facturare.nid_vanzare
|
||||
AND sters = 0 AND tip <> 3)
|
||||
AND FACTURAT = 0;
|
||||
ELSE
|
||||
-- varianta "verificare cantitate ramasa": marcheaza FACTURAT=1 doar daca toata cantitatea
|
||||
-- din VANZARI_DETALII a documentului sursa a fost deja consumata in VANZARI_CANTITATI
|
||||
...
|
||||
END IF;
|
||||
END;
|
||||
```
|
||||
Flipeaza `VANZARI.FACTURAT=1` (+`ID_UTILFACT`) pe **documentul(ele) sursa** legate prin
|
||||
`VANZARI_CORESP` de documentul tocmai scris — fie neconditionat (`V_VERIFICARE=0`), fie doar cand
|
||||
cantitatea documentului sursa a fost epuizata (`V_VERIFICARE=1`, mecanismul de **facturare partiala** a
|
||||
avizelor, bazat pe `VANZARI_CANTITATI`).
|
||||
|
||||
**Argumentul 1 — nu exista un `TIP` cu care sa se cupleze corect azi.** `marcheaza_facturat` se cheama
|
||||
azi **doar** din `finalizeaza_factura`, in aceleasi doua ramuri `WHEN pack_facturare.ntip = 4` si
|
||||
`WHEN pack_facturare.ntip = 24` care scriu si corespondenta (`:14823-14831`) — cuplate mereu impreuna cu
|
||||
`scrie_corespondente_vanzari(1)`/`(2)`, niciodata separat. Cum am stabilit la Capcana (i), calea propusa
|
||||
pentru `TIP=4` (factura din proforma) e un **apel explicit separat**, in afara acestui `CASE` — deci
|
||||
n-ar exista niciun loc "natural" unde `marcheaza_facturat` sa se agate fara sa introduca exact acelasi
|
||||
tip de cod nou-scris ca la corespondenta insasi.
|
||||
|
||||
**Argumentul 2 — mecanismul de "cantitate ramasa" (`V_VERIFICARE=1`) nu are pe ce sa opereze pentru o
|
||||
proforma.** Verificarea citeste `VANZARI_CANTITATI` (populat **doar** de
|
||||
`scrie_cantitati_vanzari_avize`, apelata **doar** in ramura `ntip=4` a lui `finalizeaza_factura`).
|
||||
Proforma nu trece niciodata prin `finalizeaza_factura` (confirmat in
|
||||
`s5b_proiectare_proforma_copiere.md`, "Descoperire centrala": `scrie_proforma` cheama doar
|
||||
`scrie_in_vanzari`) — deci **nu exista niciodata randuri `VANZARI_CANTITATI` pentru o proforma**.
|
||||
Varianta `V_VERIFICARE=1` a lui `marcheaza_facturat` ar gasi mereu "cantitate ramasa = cantitate totala"
|
||||
(nimic consumat), fie nu ar marca niciodata `FACTURAT=1` (comportament inutil), fie (daca s-ar folosi
|
||||
gresit `V_VERIFICARE=0`) ar marca neconditionat, ca la punctul urmator.
|
||||
|
||||
**Argumentul 3 — asimetrie la stergere: proforma ar ramane blocata `FACTURAT=1` definitiv.**
|
||||
`sterge_factura` (`PACK_FACTURARE:5501-5533`) reface `FACTURAT=0` pe documentele sursa **doar** pentru
|
||||
`V_TIP=24` (aviz retur, `:5502-5510`) si `V_TIP=4` (factura din aviz, `:5525-5533`) — tipurile care azi
|
||||
chiar cheama `marcheaza_facturat`. Rezultatul copierii unei proforme cade intotdeauna in `{1,5,7,10}`
|
||||
(per (b)) — **niciuna din aceste valori nu are ramura in `CASE`-ul de stergere**. Daca s-ar chema
|
||||
`marcheaza_facturat` la scrierea facturii din proforma, iar utilizatorul ar sterge ulterior acea
|
||||
factura, **proforma sursa ar ramane cu `FACTURAT=1` pentru totdeauna** — o stare orfana, fara niciun
|
||||
cod care s-o repare, introdusa exact de acest apel. Corespondenta `VANZARI_CORESP` insasi nu are aceeasi
|
||||
problema (nimic n-o citeste ca sa se strice daca ramane "orfana" dupa stergerea facturii — cel mult
|
||||
devine o legatura catre un document sters, inofensiv).
|
||||
|
||||
**Argumentul 4 — nimic nu filtreaza azi proformele dupa `FACTURAT`, deci n-ar exista niciun beneficiu
|
||||
de blocat.** Spre deosebire de avize (`cursor_avize`/candidatii de facturat, filtrati implicit prin
|
||||
fluxul dedicat "Factura din aviz") si comenzi (`VCOMENZI.FACTURAT=0`, folosit explicit in
|
||||
`cauta_date_comanda`/`cauta_date_comanda_gest`, `PACK_FACTURARE:15659,15685`, ca sa nu ofere din nou o
|
||||
comanda deja facturata), **nimic in codul citit** filtreaza dupa `FACTURAT` cand se alege o proforma ca
|
||||
sursa de copiere — confirmat la (a): `IsCopy`/vizibilitatea butonului/filtrul de grid nu se uita
|
||||
niciodata la `facturat`. Marcarea n-ar preveni nicio re-copiere accidentala a aceleiasi proforme (care
|
||||
oricum ramane posibila, vezi propunerea finala, punctul de decizie 2).
|
||||
|
||||
**Concluzie**: `marcheaza_facturat` **nu trebuie chemat** pentru "factura din proforma". Se scrie
|
||||
**doar** corespondenta (`VANZARI_CORESP`, `TIP=4`), fara actualizare de `VANZARI.FACTURAT` pe proforma.
|
||||
Confirma explicit suspiciunea din misiune ("proforma nu e document de livrare") cu evidenta concreta:
|
||||
proforma n-are urma in `VANZARI_CANTITATI` (Argumentul 2), n-are ramura de reversare la stergere
|
||||
(Argumentul 3), si n-are niciun consumator care sa citeasca `FACTURAT` pe ea (Argumentul 4).
|
||||
|
||||
## Propunere de proiectare
|
||||
|
||||
Mecanismul de baza **ramane copierea existenta** (`do_copiaza`/`copiere_factura`/`factureaza`,
|
||||
neschimbata in structura ei) — cerinta "document nou, nu comutare pe acelasi document" e deja
|
||||
satisfacuta de calea de azi (confirmat la (a)/(b)). Ce lipseste e strict **legatura persistata**
|
||||
proforma → factura si excluderea explicita a lui `marcheaza_facturat`. Pasi, in ordine:
|
||||
|
||||
1. **Captureaza `eproforma` al sursei la copiere.** In `completeaza_setari_document`
|
||||
(`COMUN\programe\ofacturare_comun.prg:387`, ramura `tlFactura=.T.`), langa
|
||||
`.listaid = toDateAnterior.id_vanzare`, adauga o proprietate noua pe `poDate`
|
||||
(ex. `.lProformaSursa`), citind `toDateAnterior.eproforma` — singurul loc unde informatia mai e
|
||||
disponibila, inainte sa se piarda (Capcana (i)). *Gata cand:* dupa copierea unei proforme,
|
||||
`poDate.lProformaSursa = .T.`; dupa copierea oricarui alt document, `.F.`.
|
||||
2. **Rezerva `TIP=4`** in `VANZARI_CORESP` pentru "factura scrisa dintr-o proforma" — doar o conventie
|
||||
documentata (ca la 1/2/3), zero schimbare de schema (confirmat la (c): tabela ramane
|
||||
`ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP, STERS`).
|
||||
3. **Scrie corespondenta cu un apel Oracle explicit, separat de `finalizeaza_factura`.** Imediat dupa
|
||||
ce `do_scrie_factura` confirma succesul (acelasi punct unde azi se decid listarea/atasamentele),
|
||||
daca `poDate.lCopiere AND poDate.lProformaSursa`: `{call
|
||||
pack_facturare.scrie_corespondente_vanzari(4)}` — **reutilizeaza procedura Oracle existenta,
|
||||
neschimbata** (ramura `ELSE`, deja scrisa pentru `TIP=2`/`3`). Zero cod PL/SQL nou pentru scriere.
|
||||
*Alternativa mai robusta, cu cost:* o mica procedura Oracle noua care primeste explicit
|
||||
`V_ID_VANZARE_FACT`/`V_ID_VANZARE_PROFORMA` ca parametri (nu se bazeaza pe
|
||||
`pack_facturare.clistaid`/`nid_vanzare` inca valide in sesiune) — de ales intre simplitate (varianta
|
||||
de mai sus) si robustete fata de ordinea apelurilor (varianta cu parametri expliciti); vezi punctul
|
||||
de decizie 1 mai jos.
|
||||
4. **NU cheama `marcheaza_facturat`.** Confirmat cu 4 argumente independente la Capcana (ii) — proforma
|
||||
nu are `VANZARI_CANTITATI`, nu are ramura de reversare la stergere, nimic n-o filtreaza dupa
|
||||
`FACTURAT`, si oricum n-ar exista un `WHEN pack_facturare.ntip=...` natural de unde s-o cheme (calea
|
||||
aleasa la pasul 3 e explicit separata de `finalizeaza_factura`).
|
||||
5. **Afisare optionala "provine din proforma X"** pe factura noua — se poate construi din
|
||||
`VANZARI_CORESP` (`TIP=4`) la fel ca tiparul deja folosit pentru retur (`legatura_linie_retur.md`
|
||||
§5): la nivel de **document**, nu de linie (nicio schimbare fata de tiparul deja acceptat pentru
|
||||
retur — nu exista niciun tabel de legatura la nivel de linie in tot codul citit, per acelasi raport).
|
||||
Nu e cerut explicit de misiune, dar e disponibil "gratis" din legatura scrisa la pasul 3.
|
||||
|
||||
**Ce NU se schimba** (confirmat, fara nevoie de atingere):
|
||||
- `IsCopy`, vizibilitatea `But_copiaza1`, filtrul de grid "Facturi&Avize / Proforme" — proforma ramane
|
||||
selectabila ca sursa exact ca azi (a).
|
||||
- Degradarea de tip din `do_copiaza` (`{1,5,7,10}` pentru orice proforma) si alocarea de numar nou —
|
||||
raman neschimbate, deja produc `nIdTipDoc=5`/`eproforma=0` corect pentru documentul nou (b).
|
||||
- `cursor_retur_document`, restaurarea `GESTIONABIL=B.IN_STOC` la copiere — neschimbat, mecanism deja
|
||||
corect pentru orice copiere (mostenit din `s5b_proiectare_proforma_copiere.md` §7).
|
||||
- `scrie_corespondente_vanzari` insasi — zero cod PL/SQL nou (varianta recomandata la pasul 3).
|
||||
|
||||
### Riscuri si ce ramane de decis de Marius
|
||||
|
||||
1. **Apel explicit pe stare de sesiune (`clistaid`/`nid_vanzare`) vs. procedura noua cu parametri
|
||||
expliciti** (pasul 3) — simplitate (zero cod Oracle nou, dar depinde ca nimic altceva sa nu resetteze
|
||||
starea pachetului intre scrierea facturii si apelul de corespondenta) vs. robustete (un obiect
|
||||
PL/SQL nou, insensibil la ordine). Recomandare: varianta simpla, **daca** apelul se plaseaza imediat
|
||||
dupa `do_scrie_factura`, inainte de orice alt apel Oracle din acelasi flux (listare/atasamente nu
|
||||
ating Oracle pentru scriere) — de verificat pe cod exact la implementare ca nu exista un apel Oracle
|
||||
intercalat care ar reseta `pack_facturare.nid_vanzare`.
|
||||
2. **Poate fi copiata de mai multe ori aceeasi proforma?** Azi nu exista nicio garda (FACTURAT nu se
|
||||
marcheaza — decizia de la Capcana (ii)), deci un utilizator poate genera N facturi din aceeasi
|
||||
proforma, fiecare cu propriul rand `TIP=4` in `VANZARI_CORESP` catre aceeasi proforma sursa. E
|
||||
comportamentul implicit al oricarei copieri azi (nimic n-o limiteaza nici pentru facturi/avize
|
||||
normale) — de confirmat daca e acceptabil sau daca se doreste un avertisment (nu o blocare, pentru
|
||||
ca ar contrazice tiparul existent de copiere liber-repetabila).
|
||||
3. **Guard simetric la stergere?** `sterge_factura` blocheaza azi stergerea unui aviz/unei facturi care
|
||||
are deja o factura de retur legata (`TIP=3`) sau facturi/avize-retur legate (`TIP IN (1,2)`,
|
||||
`PACK_FACTURARE:5450-5476`). Nu exista cerinta explicita in misiune pentru un guard simetric pe
|
||||
`TIP=4` (ex. "nu poti sterge o proforma care are deja o factura generata din ea") — de decis daca se
|
||||
doreste, sau daca proforma ramane liber stersa oricand (comportament actual, neschimbat daca nu se
|
||||
adauga nimic).
|
||||
4. **Afisarea "provine din proforma X"** (pasul 5) — optionala, nu ceruta explicit; de decis daca
|
||||
merita implementata acum sau ramane pentru o poveste ulterioara (costul e mic, data fiind legatura
|
||||
deja scrisa la pasul 3).
|
||||
5. **`TIP=4` ca valoare aleasa** — libera si fara conflict azi (confirmat cod+date), dar e o alocare
|
||||
ireversibila din punct de vedere al datelor odata folosita in productie; de confirmat explicit de
|
||||
Marius inainte de implementare (nu doar acceptata tacit).
|
||||
|
||||
## STARE / CE RAMANE
|
||||
|
||||
**Cercetare + proiectare incheiate.** Toate cele trei intrebari (a/b/c) si ambele capcane (i/ii) au
|
||||
raspuns cu dovada `fisier:linie`, verificat pe fisierele text reale (`ofacturare_comun.vc2`,
|
||||
`ofacturare.vc2`, `ofacturare_comun.prg`, `ofacturare.prg` — nu `.bak`) si pe `PACK_FACTURARE`
|
||||
(`ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`), plus verificare directa pe schema Oracle vie
|
||||
(`MARIUSM_AUTO`: structura `VANZARI_CORESP`, distributia `TIP` din date, singurul trigger existent).
|
||||
|
||||
Descoperirea centrala a acestei cercetari: mecanismul de copiere de azi rezolva deja "document nou" si
|
||||
"tip corect" (a, b) fara nicio schimbare, dar **pierde tacit** informatia "sursa era o proforma" chiar
|
||||
in metoda care ar trebui s-o pastreze (`completeaza_setari_document:387`, care copiaza `listaid` dar nu
|
||||
si `eproforma`) — motiv pentru care legatura `VANZARI_CORESP` nu se poate agata de `CASE`-ul existent
|
||||
din `finalizeaza_factura` (cheie doar pe `ntip`, care nu poarta aceasta distinctie) si are nevoie de un
|
||||
semnal nou, client-side, plus un apel Oracle explicit separat (pasii 1 si 3 din propunere).
|
||||
|
||||
Niciun fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, niciun commit, nicio scriere pe
|
||||
Oracle (doar `SELECT`, rulat cu `sqlplus.exe` pe schema `MARIUSM_AUTO`).
|
||||
720
docs/cercetare/s8_incarcare_document.md
Normal file
720
docs/cercetare/s8_incarcare_document.md
Normal file
@@ -0,0 +1,720 @@
|
||||
# S8 — Proiectare: incarcarea documentului existent in formularul unificat
|
||||
|
||||
Livrabilul povestii **S8** din `docs\plan_13_unificare_formular_facturare.md:2944-2962` (etapa II,
|
||||
deciziile 5, 8, 9, 19, 35, 50). Cercetare **READ-ONLY** — zero editari de cod, zero write-back, zero
|
||||
`git_sync.ps1` / `txt2vcx.ps1`, zero commit. Pe Oracle **numai `SELECT`** pe dictionar
|
||||
(`all_tab_columns` pentru `VANZARI`, `VANZARI_DETALII`, `FACT_VFACTURI`, `FACT_VFACTURI_DETALII`).
|
||||
`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar
|
||||
citite, neatinse.
|
||||
|
||||
**Conventie de marcare**, folosita peste tot in raport:
|
||||
- **[V]** = verificat direct pe cod / pe dictionarul Oracle, cu `fisier:linie` sau nume de coloana;
|
||||
- **[D]** = dedus din cod verificat, dar concluzia insasi n-a fost executata / observata;
|
||||
- **[N]** = nestabilit, cu motivul scris.
|
||||
|
||||
---
|
||||
|
||||
## 0. Verdict (rezumat)
|
||||
|
||||
Schita din plan — *„`completeaza_setari_document` pentru antet + `cursor_retur_document(V_COPIERE = 1)`
|
||||
pentru linii, fara degradarea de tip din `do_copiaza`"* — e corecta ca directie, dar **niciuna dintre
|
||||
cele doua piese nu incarca documentul**, si asta nu e o nuanta de implementare:
|
||||
|
||||
1. **`completeaza_setari_document(toFactura, .T.)` copiaza 16 proprietati din ~120 [V]**
|
||||
(`COMUN\programe\ofacturare_comun.prg:361-411`). Nu copiaza **niciunul** dintre cei 14 parametri de
|
||||
identitate/antet ai lui `modifica_date_factura`, in afara de `id_delegat` / `id_masina` / `id_agent`.
|
||||
Serie, numar, data, scadenta, `text_aditional`, `id_facturare`, `id_ruta`, `tip_saft`, `efactura`,
|
||||
`listare_detaliata`, `dataora_exp`, discountul de document, `discount_evidentiat`, `eproforma`,
|
||||
valuta, cursul — **toate lipsesc**. Sectiunea 1.2 le da pe toate, cu sursa.
|
||||
2. **`cursor_retur_document(V_COPIERE = 1)` nu umple gridul documentului, ci gridul-sursa.** Pe calea de
|
||||
copiere, rezultatul lui intra in `crsarticole` (selectorul din stanga), iar `crsfactura` (documentul)
|
||||
se creeaza **gol** si ramane gol — comentariul e explicit: *„Nu adaug automat articolele din factura
|
||||
originala, ca sa dau posibilitate de modificare"* (`COMUN\programe\ofacturare.prg:455-457`,
|
||||
`:338` `creeaza_facturacrs([crsfactura])`) **[V]**. Transferul in document se face **numai** prin
|
||||
`do_adauga_tot` → `do_adauga_articol`, care pentru articolele gestionabile trece prin
|
||||
**dialogul de alegere din stoc** (`do_alege_stoc`). Sectiunea 4.3.
|
||||
3. **`cursor_retur_document` nu intoarce `ID_VANZARE_DET` si nu intoarce `TAXCODE` [V]**
|
||||
(lista completa de coloane: `PACK_FACTURARE:3949-4054`). Fara `ID_VANZARE_DET`:
|
||||
- **cheia de linie presupusa de S8b (§1.2 al raportului S8b) nu exista** pe calea propusa;
|
||||
- **ruta ieftina `modifica_explicatie_articol` devine neapelabila** — primul ei parametru **este**
|
||||
`V_ID_VANZARE_DET` (`PACK_FACTURARE:14511-14519`) **[V]**.
|
||||
Fara `TAXCODE`, cerinta explicita a planului („explicatia si `taxcode` pe fiecare linie") nu e
|
||||
acoperita.
|
||||
|
||||
**Recomandarea centrala a acestei proiectari:** S8 **nu** se construieste pe perechea din schita, ci pe
|
||||
**precedentul care exista deja si face exact acest lucru** — `relisteaza_ofacturare_stoc`
|
||||
(`COMUN\programe\ofacturare_stoc.prg:456-742`) **[V]**, care reconstituie `poDate` + cursorul de linii
|
||||
dintr-un document salvat, prin `FACT_VFACTURI` (antet) + `FACT_VFACTURI_DETALII` (linii), pentru
|
||||
relistare. `FACT_VFACTURI_DETALII` **are** `ID_VANZARE_DET` si `TAXCODE` **[V]**. Detaliile,
|
||||
compromisurile si ce ramane totusi de completat — sectiunea 1.3 si 4.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventarul complet a ce se incarca, camp cu camp
|
||||
|
||||
### 1.1 Cele doua canale de citire, comparate pe coloane [V]
|
||||
|
||||
Sursa: `PACK_FACTURARE:3949-4054` (`cursor_retur_document`) si `all_tab_columns` pentru
|
||||
`FACT_VFACTURI_DETALII` / `VANZARI_DETALII`.
|
||||
|
||||
| Ce trebuie pe linie | `cursor_retur_document(V_COPIERE=1)` | `FACT_VFACTURI_DETALII` | Exista pe `VANZARI_DETALII`? |
|
||||
|---|---|---|---|
|
||||
| `ID_VANZARE_DET` (cheia liniei) | **NU** | **DA** | DA |
|
||||
| `TAXCODE` | **NU** | **DA** | DA |
|
||||
| `EXPLICATIE` | DA | DA | DA |
|
||||
| `ID_POL` (politica de pret pe linie) | DA | **NU** (doar `NUME_LISTA_PRETURI`) | DA |
|
||||
| `PRETD` / `ID_VALUTAD` | DA (`PRETD`) | **NU** | DA |
|
||||
| `GESTIONABIL` (flag calculat) | DA (calculat, vezi 1.4) | **NU** | — (derivat din `ID_GESTIUNE`) |
|
||||
| `DIFERENTA` | pliata in `PRET` (vezi 1.4) | **NU** | DA |
|
||||
| `ID_GESTIUNE`, `LOT`, `SERIE`, `CANTITATE`, `PRET`, `PRET_CU_TVA`, `PROC_TVAV`, `ID_JTVA_COLOANA`, `ID_JTVA_COLOANA_EX`, `PRET_ACHIZITIE`, `DISCOUNT_UNITAR`, `ID_VALUTA`, `CURS`, `MULTIPLICATOR` | DA | DA | DA |
|
||||
| `ID_CTR` / `NUMAR_CONTRACT` | **NU** | **DA** | DA (`ID_CTR`) |
|
||||
|
||||
**Concluzia operationala: niciunul dintre cele doua canale nu e suficient singur [V].** Cele doua
|
||||
coloane care lipsesc din `cursor_retur_document` (`ID_VANZARE_DET`, `TAXCODE`) exista **amandoua pe
|
||||
`VANZARI_DETALII`**, deci sunt disponibile fara nicio schimbare de structura — lipseste doar din lista
|
||||
`SELECT`.
|
||||
|
||||
**Trei variante, cu recomandare:**
|
||||
|
||||
| Varianta | Ce cere | Risc |
|
||||
|---|---|---|
|
||||
| **(A)** adaugarea celor doua coloane in `SELECT`-ul lui `cursor_retur_document` | doua randuri in PL/SQL, plus `A1.ID_VANZARE_DET, A1.TAXCODE` in subselectul intern | Procedura e folosita si de **copiere** si (prin `cursor_retur`) de **retur**. Coloanele in plus ajung in `crsarticole` → `Scatter Name poArticol` → si `taxcode` **s-ar gathera** in `crsfactura` (campul exista deja acolo, `creeaza_facturacrs`, `ofacturare_comun.prg:1785`) **[V]**. Adica **copierea ar incepe sa transporte `taxcode`** — probabil o corectie, dar e o **schimbare de comportament pe o cale existenta**, nu una inerta. |
|
||||
| **(B) procedura noua**, `cursor_editare_document`, clona cu cele doua coloane in plus — **RECOMANDAT** | o procedura noua in `pack_facturare`, zero atingere pe cele existente | Duplicare de SQL (~100 randuri). Costul e intretinerea, nu regresia. Garantie de regresie zero **prin constructie**, in acelasi spirit ca solutia `SET_IDFACT` din S9. |
|
||||
| **(C)** citire din `FACT_VFACTURI_DETALII` (calea `relisteaza_ofacturare_stoc`) | zero cod Oracle nou | Pierde `ID_POL`, `PRETD`/`ID_VALUTAD`, `GESTIONABIL` si tratamentul `DIFERENTA`/valuta din `cursor_retur_document` — adica exact partea grea. Ar cere refacerea ei in VFP. |
|
||||
|
||||
*Recomandare:* **(B)**, cu `SELECT`-ul copiat identic din `cursor_retur_document` plus
|
||||
`A1.ID_VANZARE_DET` si `A1.TAXCODE`, si cu ramura `GESTIONABIL` decisa explicit (1.4).
|
||||
|
||||
### 1.2 Antetul — ce se incarca, si de unde [V]
|
||||
|
||||
`poDate` e `oDateFactura` (`COMUN\programe\ofacturare_comun.prg:99-320`). Coloana „sursa" e coloana din
|
||||
`FACT_VFACTURI` (view-ul din spatele `crsFacturi`, folosit deja de `do_modifica`
|
||||
`ofacturare_comun.vc2:4560-4568` si de `relisteaza_ofacturare_stoc` `ofacturare_stoc.prg:504-509`),
|
||||
sau `VANZARI` direct.
|
||||
|
||||
Legenda coloanei „azi": **CSD** = acoperit de `completeaza_setari_document(…, .T.)`; **GOL** = nimeni
|
||||
nu-l incarca azi pe calea de copiere, deci e **gol de umplut in S8**.
|
||||
|
||||
#### (a) Identitatea documentului — antetul vizibil
|
||||
|
||||
| `poDate.` | Sursa | Azi | Nota |
|
||||
|---|---|---|---|
|
||||
| `nIdTipDoc` (FACTURA/PROFORMA/BON) | derivat din `tip` + `eproforma` | **GOL** — linia e **comentata explicit** in CSD (`ofacturare_comun.prg:366`) | vezi 3.2 |
|
||||
| `tip` | `FACT_VFACTURI.TIP` | **GOL** (si azi **degradat** de `do_copiaza`) | S8 il pastreaza, per plan |
|
||||
| `eProforma` | `FACT_VFACTURI.EPROFORMA` | **GOL** — `eproforma` nu apare deloc in `ofacturare_comun.prg` (rezultatul 1 al rundei 13) | |
|
||||
| `serie_act` | `FACT_VFACTURI.SERIE_ACT` | **GOL** (CSD il pune doar in `.descriere`, ca text) | vezi 4.2 — conflict cu `poGeneratorNumere` |
|
||||
| `nract` | `FACT_VFACTURI.NUMAR_ACT` | **GOL** (idem) | idem |
|
||||
| `dataact` | `FACT_VFACTURI.DATA_ACT` | **GOL** (`Init` pune **azi**) | |
|
||||
| `datascad` | `FACT_VFACTURI.DATA_SCAD` | **GOL** (`Init` calculeaza din `gnZileScadentaFact`) | |
|
||||
| `zi_curs` | **NU EXISTA COLOANA** — vezi 1.5 | **GOL, si nerecuperabil** | raspuns la intrebarea deschisa din S8b §5.2 |
|
||||
| `in_valuta` / `id_valuta` / `nume_valuta` | `FACT_VFACTURI.IN_VALUTA` / `ID_VALUTA` / `NUME_VAL` | **GOL** (`Init` deriva `in_valuta` din `tip`) | |
|
||||
| `Curs` / `multiplicator` | `FACT_VFACTURI.CURS` / `MULTIPLICATOR` | **GOL** | pe linii, din `VANZARI_CURSURI` |
|
||||
|
||||
#### (b) Partener si sursa
|
||||
|
||||
| `poDate.` | Sursa | Azi |
|
||||
|---|---|---|
|
||||
| `id_client` / `nume_client` | `ID_PART` / `CLIENT` | **CSD** |
|
||||
| `cod_fiscal` | `FACT_VFACTURI.COD_FISCAL` | **GOL** |
|
||||
| `sold_lei` / `sold_valuta` | recalculate la deschidere (derivate, read-only) | **GOL** — se recalculeaza, nu se restaureaza |
|
||||
| `listaid` (id comanda / contract / lista avize) | vezi 1.6 | **CSD, dar cu valoare GRESITA pentru editare** — CSD scrie `.listaid = toDateAnterior.id_vanzare` (`ofacturare_comun.prg:383`) **[V]** |
|
||||
| `descriere` (nr. comanda/contract/avize) | `FACT_VFACTURI.ALTELE` / `COMANDA` / `CONTRACT` / `AVIZE` | **CSD, dar cu serie+numarul documentului insusi**, nu al sursei (`:384`) |
|
||||
| `id_gestiune_init` | `FACT_VFACTURI.ID_GESTIUNE` | **GOL** |
|
||||
| `id_pol` / `nume_politica` | **NU EXISTA pe `VANZARI`** — doar `VANZARI_DETALII.ID_POL` | **GOL** — vezi 1.5 |
|
||||
| `id_ctr` / `contract` | `VANZARI.ID_CTR` / `VANZARI.CONTRACT` | **GOL** |
|
||||
|
||||
#### (c) Pliat — analitice (grupul A, read-only in #13, decizia 26)
|
||||
|
||||
| `poDate.` | Sursa | Azi |
|
||||
|---|---|---|
|
||||
| `id_sectie` / `sectie` | `FACT_VFACTURI.ID_SECTIE` / `SECTIE` | **CSD** |
|
||||
| `id_lucrare` / `nrord` | `ID_LUCRARE` / `LUCRARE` | **CSD** |
|
||||
| `id_responsabil` / `responsabil` | **NU EXISTA pe `VANZARI`** | **GOL, si nerecuperabil din `VANZARI`** — liniile sunt **comentate** in CSD (`:369-370`) **[V]** |
|
||||
| `id_venchelt` / `venchelt` | **NU EXISTA pe `VANZARI`** | **GOL** — idem, comentate (`:373-374`) **[V]** |
|
||||
|
||||
> Faptul ca `ID_RESPONSABIL` si `ID_VENCHELT` **nu sunt coloane pe `VANZARI`** (verificat pe
|
||||
> `all_tab_columns`) e **dovada structurala** ca grupul A traieste la nivel de **linie de nota**, nu de
|
||||
> antet — exact ce spunea `rute_scriere_antet.md`, dar acum cu argument de schema, nu de cautare. **[V]**
|
||||
|
||||
#### (d) Pliat — alte date (`frm_alte_date`) si cei 14 parametri
|
||||
|
||||
| `poDate.` | Sursa | Azi |
|
||||
|---|---|---|
|
||||
| `id_delegat` / `nume_delegat` / `BIdelegat` / `CNPdelegat` | `ID_DELEGAT` / `DELEGAT` / `BIDELEGAT` / `CNPDELEGAT` | **CSD** |
|
||||
| `id_masina` / `nrinmat` | `ID_MASINA` / `NRINMAT` | **CSD** |
|
||||
| `id_agent` / `nume_agent` | `ID_AGENT` / `NUME_AGENT` | **CSD** |
|
||||
| `id_facturare` / `adresa_facturare` | `ID_FACTURARE` / `ADRESA_FACTURARE` | **GOL** |
|
||||
| `dataora_exp` | `DATAORA_EXP` | **GOL** |
|
||||
| `text_aditional` | `TEXT_ADITIONAL` | **GOL** — plus capcana de la 1.7 |
|
||||
| `nListareDetaliata` | `LISTARE_DETALIATA` | **GOL** |
|
||||
| `tip_saft` | `TIP_SAFT` | **GOL** (`Init` pune `380`) |
|
||||
| `eFactura` | `EFACTURA` | **GOL** (`Init` pune `0`) |
|
||||
| *`id_ruta`* | `ID_RUTA` | **GOL, si nu exista proprietate pe `oDateFactura`** — vezi 1.5 |
|
||||
| `afisare_scadenta` | `AFISARE_SCADENTA` | **GOL** (`Init` pune `1`; ROACONTRACTE il recalculeaza) |
|
||||
| `tva_incasare` | `TVA_INCASARE` | **GOL** (`Init` ia `goCalendar.tva_incasare`) |
|
||||
| `institutie_publica` | `FACT_VFACTURI.INSTITUTIE_PUBLICA` | **GOL** |
|
||||
| `nTipFactura` | `TIP_FACTURA` | **GOL** |
|
||||
| `nIdBeneficiar` | `ID_BENEFICIAR` | **GOL** |
|
||||
| `id_ordl` | `ID_ORDL` | **GOL** |
|
||||
| `coeficient_k` | `VANZARI.COEFICIENT_K` | **GOL** |
|
||||
| grupul C (`ntip_incasare`, `serie_chit`, `nr_incasare`, `incasat`, `id_casa`) | `TIP_INCASAT`, `SERIE_INCASAT`, `NR_INCASAT`, `SUMA_INCASAT` | **GOL** — blocat in etapa I, dar vezi 2.3 |
|
||||
|
||||
#### (e) Discountul de document — nu e in `poDate`
|
||||
|
||||
**[V]** Discountul de document **nu are proprietate pe `oDateFactura`**: traieste in proprietatile
|
||||
formularului `frm_facturare_articole.ndiscfactron` / `.ndiscfactval`
|
||||
(`ofacturare.vc2:5286-5287` declaratie `*p:`, `:5317-5318` initializare la `0`,
|
||||
`:11325`/`:11337` `ControlSource` pe cele doua textbox-uri, `:14272-14274` citirea la scriere).
|
||||
|
||||
| Ce | Sursa | Cum se incarca |
|
||||
|---|---|---|
|
||||
| discount de document, lei | `VANZARI.DISCOUNT` (in lei cand `IN_VALUTA=0`) | `Thisform.ndiscfactron` |
|
||||
| discount de document, valuta | `VANZARI.DISCOUNT` (in valuta cand `IN_VALUTA=1`) | `Thisform.ndiscfactval`, plus `ndiscfactron = Round(ndiscfactval * Curs / multiplicator, …)` |
|
||||
| `discount_evidentiat` | `VANZARI.DISCOUNT_EVIDENTIAT` | `poDate.discount_evidentiat` (`ControlSource` la `ofacturare.vc2:11305` si `:15968`) |
|
||||
|
||||
**Precedentul exact exista** si trateaza deja bifurcatia lei/valuta:
|
||||
`ofacturare_stoc.prg:553-561` **[V]** — `lnDiscountVal = discount` cand `in_valuta = 1`, altfel
|
||||
`lnDiscount = discount`. S8 copiaza acest tipar.
|
||||
|
||||
**Atentie [V]:** `oDateFactura.Init` pune `.discount_evidentiat = IIF(TYPE('gnDiscountEvidentiat')='N',
|
||||
gnDiscountEvidentiat, 1)` (`ofacturare_comun.prg:238`) — **optiunea de firma, nu valoarea documentului**.
|
||||
Daca S8 nu suprascrie, un document emis pe alta setare apare cu discountul evidentiat altfel decat a
|
||||
fost emis, si — pentru ca `discount_evidentiat` **atinge sumele** (e parametru al lui `scrie_factura2`,
|
||||
`ofacturare.vc2:14358`) — ar declansa **regenerare la simpla deschidere**. Fals pozitiv de acelasi tip
|
||||
cu lookup-ul delegatului, dar cu efect mai scump.
|
||||
|
||||
### 1.3 Liniile — inventar
|
||||
|
||||
Coloanele intoarse de `cursor_retur_document(V_COPIERE=1)`, in ordinea din `SELECT`
|
||||
(`PACK_FACTURARE:3963-4022`) **[V]**: `ID_C` (=`ROWNUM`), `ID_ARTICOL`, `LOT`, `SERIE`, `ID_POL`,
|
||||
`ID_VALUTA`, `DISCOUNT_UNITAR`, `DISCOUNT_UNITAR_VAL`, `CODMAT`, `CODBARE`, `DENUMIRE`, `UM`,
|
||||
`GESTIONABIL`, `CANTITATE`, `PROC_TVAV`, `ID_JTVA_COLOANA`, `PRETURI_CU_TVA`, `CURS`, `MULTIPLICATOR`,
|
||||
`PRET`, `PRET_VAL`, `TIP_VALUTA`, `NUME_VAL`, `EXPLICATIE`, `ID_GESTIUNE`, `PRET_ACHIZITIE`, `PRETD`,
|
||||
`ID_JTVA_COLOANA_EX`.
|
||||
|
||||
**Lipsesc, si sunt necesare pentru S8/S8b:** `ID_VANZARE_DET`, `TAXCODE`, `ID_CTR`, `CODMATC`,
|
||||
`COD_UM_ISO`, `CONT`. Ultimele trei sunt in `crsfactura` si vin azi pe alte cai
|
||||
(`do_adauga_articol` cere `codmatf` cu un `SELECT` separat, `ofacturare.vc2:12932-12934` **[V]**).
|
||||
|
||||
### 1.4 Doua capcane in `cursor_retur_document`, ambele verificate direct
|
||||
|
||||
**(i) `GESTIONABIL` cu `V_COPIERE = 1` vine din nomenclatorul CURENT, nu din document [V]**
|
||||
(`PACK_FACTURARE:3990-3997`):
|
||||
|
||||
```
|
||||
(case when V_PROFORMA = 1 then 0
|
||||
when V_COPIERE = 1 then B.IN_STOC -- NOM_ARTICOLE, starea de AZI
|
||||
else A.GESTIONABIL -- NVL2(A1.ID_GESTIUNE,1,0), starea DOCUMENTULUI
|
||||
end) AS GESTIONABIL
|
||||
```
|
||||
|
||||
Pentru **copiere** e corect (documentul nou se emite cu regulile de azi). Pentru **editare** e o
|
||||
divergenta tacuta: un articol care a fost facturat gestionabil si intre timp a fost trecut pe
|
||||
`IN_STOC = 0` (sau invers) se incarca cu **alt regim** decat cel cu care e scris in `VANZARI_DETALII`.
|
||||
Efectul practic: articolul trece pe alta ramura in `do_adauga_articol` (dialog de stoc vs. fara),
|
||||
deci se schimba si ce se descarca la reemitere. **Recomandare: pe calea de editare se foloseste
|
||||
`A.GESTIONABIL` (ramura `else`), adica adevarul documentului** — argument suplimentar pentru varianta
|
||||
(B) din 1.1. **Aceasta e a cincea valoare din rezultatul 6 al handoff-ului (`IN_STOC` din nomenclator),
|
||||
regasita pe calea de CITIRE, nu doar pe cea de scriere.**
|
||||
|
||||
**(ii) `PRET` vine rotunjit si cu `DIFERENTA` pliata inauntru [V]** (`PACK_FACTURARE:4008-4014`):
|
||||
`ROUND(...) + A.DIFERENTA`. Confirma rezultatul 6 din handoff. Consecinta directa pentru **S8b**:
|
||||
comparatia de pret la confirmare trebuie facuta pe **aceasta forma** (rotunjita + `DIFERENTA`), nu pe
|
||||
`VANZARI_DETALII.PRET` brut — altfel orice document cu `DIFERENTA <> 0` apare modificat la deschidere.
|
||||
|
||||
### 1.5 Trei campuri de antet care nu au unde sa fie salvate — deci nu se pot restaura [V]
|
||||
|
||||
Verificat pe `all_tab_columns` pentru `VANZARI`:
|
||||
|
||||
| Camp | Constatare | Ce inseamna pentru S8 |
|
||||
|---|---|---|
|
||||
| **`zi_curs`** | **Nu exista coloana `ZI_CURS` pe `VANZARI`.** Se stocheaza rezultatul (`CURS`, `MULTIPLICATOR`, si `VANZARI_CURSURI` pe linie), nu ziua de la care s-a luat cursul. | **Inchide intrebarea deschisa din S8b §5.2.** Nu e „S8 trebuie sa suprascrie implicitul cu valoarea salvata" — **nu exista valoare salvata**. Se incarca `Curs`/`multiplicator` din document, iar `zi_curs` **se exclude din comparatie** si (recomandat) **se ascunde pe calea de editare**, ca sa nu sugereze o valoare pe care documentul n-o are. Ascunderea are precedent in acelasi `Do Case` (`ofacturare.vc2:9720-9725`, tipurile 8 si 9). |
|
||||
| **`id_pol`** (politica de preturi, antet) | Nu exista pe `VANZARI`; exista **pe linie** (`VANZARI_DETALII.ID_POL`). | Antetul nu are ce sa afiseze ca „politica documentului". Recomandat: pe editare se afiseaza politica **doar daca e unica pe toate liniile**, altfel gol — si e oricum **blocat** (grupul B, 3.1). |
|
||||
| **`id_ruta`** | Exista pe `VANZARI` (`ID_RUTA`) si e **unul din cei 14**, dar **`oDateFactura` nu are proprietate `id_ruta`** (verificat pe lista completa de proprietati, `ofacturare_comun.prg:99-218`). Azi valoarea circula prin `poRec` (`Scatter` din `crsfacturi`), nu prin `poDate` (`ofacturare_comun.vc2:4601`). | **Proprietate noua pe `oDateFactura`**, altfel `but_modifica` (S8c) n-are de unde sa citeasca ruta. Gol de umplut, semnalat aici pentru ca S8c il presupune existent. |
|
||||
|
||||
### 1.6 Legatura cu sursa — `id_comanda`, `id_ctr`, lista de avize
|
||||
|
||||
Ce exista, verificat:
|
||||
|
||||
| Sursa | Unde e salvata | Citibila? |
|
||||
|---|---|---|
|
||||
| comanda | `VANZARI.ID_COMANDA` (+ `VANZARI.COMANDA`, text) **[V]** | **DA**, direct din `FACT_VFACTURI.ID_COMANDA` |
|
||||
| contract | `VANZARI.ID_CTR` (+ `VANZARI.CONTRACT`, text) **[V]** | **DA**, direct din `FACT_VFACTURI.ID_CTR` |
|
||||
| avize | `VANZARI_CORESP(ID_VANZARE_FACT, ID_VANZARE_AVIZ, TIP)` + `VANZARI.AVIZE` (text redundant) **[V]** (`PACK_FACTURARE:15494-15516`) | **DA ca date, NU ca procedura** — vezi mai jos |
|
||||
|
||||
**[V] Nu exista in `pack_facturare` nicio procedura care sa CITEASCA lista sursa a unui document.**
|
||||
Cele 9 aparitii ale lui `VANZARI_CORESP` in export sunt: 6 in garzile lui `sterge_factura`
|
||||
(`:5454-5530`), 1 `UPDATE ... STERS = 1` tot acolo (`:5582`), 1 subselect in
|
||||
`scrie_cantitati_vanzari_avize` (`:6319`), 1 `INSERT` in `scrie_corespondente_vanzari` (`:15494`).
|
||||
**Citirea e cod nou** — un `SELECT` simplu, dar de scris:
|
||||
|
||||
```sql
|
||||
SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP
|
||||
WHERE ID_VANZARE_FACT = :id AND STERS = 0 AND TIP = 1
|
||||
```
|
||||
|
||||
**Semantica lui `TIP` nu e stabilita in acest raport [N]** — `scrie_corespondente_vanzari(V_TIP)`
|
||||
comuta doar sursa listei (`clistaid_avize` pentru `TIP = 1`, `clistaid` altfel, `:15485-15493`), iar
|
||||
valorile efective (`1/2/3`) vin din `CASE`-ul lui `finalizeaza_factura`, necitit aici. Handoff-ul
|
||||
rundei 13 le da ca `1/2/3` folosite, `4` liber. **De confirmat ramura cu ramura la implementare**, nu
|
||||
de preluat din raport — e exact tiparul care a produs cifra gresita a rundei 13.
|
||||
|
||||
**Nota de proiectare:** `poDate.listaid` e **suprasolicitat**. Pe calea de copiere/editare el trebuie sa
|
||||
fie `id_vanzare`-ul documentului (ca `cursor_retur_document` sa-i citeasca liniile), dar pentru
|
||||
reemitere `pack_facturare.clistaid` / `clistaid_avize` trebuie sa contina **lista sursei originale**.
|
||||
Sunt **doua valori diferite in acelasi camp**, la momente diferite. Precedentul din
|
||||
`relisteaza_ofacturare_stoc` arata ca problema e veche si **nerezolvata**: acolo
|
||||
`poDate.listaid = Iif(poDate.tip = 3, Alltrim(Str(id_comanda)), [0])` **[V]**
|
||||
(`ofacturare_stoc.prg:531`) — cu ramura de **contract comentata** la `:529`, deci nici acel precedent
|
||||
nu restaureaza sursa pentru contracte sau avize. **S8 are nevoie de un camp separat**
|
||||
(propunere: `poDate.cListaSursa` + `poDate.cListaSursaAvize`), nu de o a doua semnificatie a lui
|
||||
`listaid`. Vezi si sectiunea 6.
|
||||
|
||||
### 1.7 `text_aditional` — trei transformari intre ce se vede si ce se salveaza [V]
|
||||
|
||||
1. **Truncat la 100 de caractere** la scriere: `LEFT(Nvl(poDate.text_aditional,[]),100)`
|
||||
(`ofacturare.vc2:14356`, identic pe toate ramurile `scrie_*`).
|
||||
2. **`CR+LF` inlocuit cu `Chr(170)`** imediat inainte:
|
||||
`poDate.text_aditional = Strtran(poDate.text_aditional, Chr(13)+Chr(10), Chr(170), 1, 100, 1)`
|
||||
(`:14281`) — **si atribuirea e pe `poDate` insusi**, deci obiectul ramane modificat dupa scriere.
|
||||
3. Pe calea `modifica_date_factura` **nu exista** nici truncare, nici substitutie
|
||||
(`ofacturare_comun.vc2:4608` trimite `?poRec.text_aditional` ca parametru legat) **[V]** —
|
||||
deci **cele doua rute salveaza forme diferite ale aceluiasi camp**.
|
||||
|
||||
**Consecinte pentru S8:** la incarcare, `Chr(170)` trebuie convertit inapoi in `CR+LF` (altfel textul
|
||||
apare pe un rand cu caractere ciudate), iar comparatia din S8b trebuie facuta pe forma **normalizata**
|
||||
(vezi 5.2). Fara asta, orice document emis cu text pe mai multe randuri apare „modificat" la
|
||||
deschidere. **Nesemnalat pana acum in niciun raport.**
|
||||
|
||||
---
|
||||
|
||||
## 2. Initializarile „pentru document nou" care trebuie sarite la editare
|
||||
|
||||
Cerinta obligatorie din S8b, rezultatul 5, plus inventarul cerut de briefing.
|
||||
|
||||
### 2.1 Lookup-ul „ultimul delegat / ultima masina" — **garda exista deja** [V]
|
||||
|
||||
`frm_alte_date.Init`, `COMUN\clase\ferestre_cere_date.vc2:3105-3208`. Codul real
|
||||
(`:3110-3136`) — si aici raportul S8b si planul descriu situatia **incomplet**:
|
||||
|
||||
```
|
||||
If poDate.eProforma = 0
|
||||
If Empty(Nvl(poDate.id_delegat,0)) And Empty(Nvl(poDate.id_masina,0)) && :3111
|
||||
poDate.id_delegat = 0
|
||||
poDate.id_masina = 0
|
||||
... cauta_date_ultima_factura[_tip](...) && :3118-3124
|
||||
```
|
||||
|
||||
**Lookup-ul e deja conditionat de „ambele goale".** Deci:
|
||||
|
||||
- pentru un document care **are** delegat sau masina salvate, e suficient ca **S8 sa populeze
|
||||
`poDate.id_delegat` / `id_masina` INAINTE de construirea lui `frm_alte_date`** — lookup-ul nu mai
|
||||
ruleaza, fara niciun cod nou. `completeaza_setari_document` le pune deja (1.2.d), deci pe calea de
|
||||
editare conditia e indeplinita **din ordinea de apel**, nu dintr-un semnal;
|
||||
- **riscul real ramane, dar e mai ingust decat il descrie S8b:** el se manifesta **exact pe documentele
|
||||
care n-au nici delegat, nici masina salvate** — un caz frecvent (multe facturi se emit fara delegat).
|
||||
Pentru acelea, lookup-ul umple `poDate` cu ultimul delegat al clientului, si atunci: ori documentul
|
||||
apare modificat, ori `modifica_date_factura` scrie **un delegat pe care documentul nu l-a avut
|
||||
niciodata**. Sub `modifica_date_factura` campul se scrie **neconditionat** (`ID_DELEGAT = V_ID_DELEGAT`,
|
||||
`PACK_FACTURARE:14462`) **[V]**, deci scrierea gresita e certa, nu probabila.
|
||||
|
||||
**Mecanismul propus (minim, si in acord cu tiparul existent):** parametru nou pe `frm_alte_date.Init`
|
||||
sau — mai ieftin si mai greu de ratat — **o proprietate pe `poDate`**, `poDate.lEditare`, citita in
|
||||
conditia de la `:3111`:
|
||||
|
||||
```
|
||||
If !poDate.lEditare And Empty(Nvl(poDate.id_delegat,0)) And Empty(Nvl(poDate.id_masina,0))
|
||||
```
|
||||
|
||||
Argumentul pentru proprietate pe `poDate` si nu parametru: `frm_alte_date` **nu e singurul** formular
|
||||
care are initializari de acest tip (2.2), `poDate` e deja obiectul citit de toate, si e acelasi tipar
|
||||
cu `lCopiere`, care exista deja pe `oDateFactura` (`ofacturare_comun.prg:212`) **[V]**.
|
||||
|
||||
**„Ce se intampla cand documentul chiar n-are delegat salvat":** cu garda de mai sus, `poDate.id_delegat`
|
||||
ramane `.NULL.` (valoarea implicita din declaratia de clasa), campul se afiseaza gol, iar la salvare
|
||||
`modifica_date_factura` primeste `NULL` si scrie `NULL` — **identic cu starea din baza**. Adica exact
|
||||
comportamentul dorit: documentul ramane cum a fost. Fara garda, valoarea devine `0` (atribuirea de la
|
||||
`:3112-3113`) **si apoi** rezultatul lookup-ului.
|
||||
|
||||
> **Diferenta fata de S8b, spusa explicit:** S8b cerea „sarirea explicita a lookup-ului"; codul real
|
||||
> arata ca sarirea e **deja implicita** pentru documentele cu delegat, si e **necesara explicit** doar
|
||||
> pentru cele fara. Concluzia lui S8b (S8 trebuie sa faca ceva) ramane valida; **motivul si perimetrul
|
||||
> se schimba**, iar asta reduce riscul de la „primul document editat al unui client cu activitate
|
||||
> recenta" la „primul document **fara delegat** al unui client cu activitate recenta".
|
||||
|
||||
### 2.2 Alte initializari de acelasi tip — inventar cu `fisier:linie` [V]
|
||||
|
||||
Cautate in `oDateFactura.Init`, `frm_alte_date.Init`, `frm_date_factura.Init`, `frm_date_aviz.Init`.
|
||||
|
||||
| # | Loc | Ce face | Efect pe calea de editare | Gravitate |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `ofacturare_comun.prg:232-236` | `.Data`, `.dataireg`, `.dataact` = azi (sau ultima zi a lunii de lucru); `.datascad` din `gnZileScadentaFact`; `.zi_curs` = azi | Documentul apare cu **data de azi** si scadenta recalculata | **Mare** — atinge 2 din cei 4 campuri de identitate |
|
||||
| 2 | `ofacturare_comun.prg:238` | `.discount_evidentiat` = `gnDiscountEvidentiat` (optiune de firma) | Vezi 1.2.e — poate declansa **regenerare** la simpla deschidere | **Mare** |
|
||||
| 3 | `ofacturare_comun.prg:233` | `.tva_incasare` = `goCalendar.tva_incasare` (starea de azi a firmei) | Un document emis in alt regim se incarca cu regimul curent | Medie |
|
||||
| 4 | `ofacturare_comun.prg:237` | `.in_valuta = 1` pentru `tnIdSet` / `tnTip` din lista | Derivat din tip, nu din document — coincide de obicei, dar nu prin constructie | Mica |
|
||||
| 5 | `ofacturare_comun.prg:241-243` | `.initializeaza_politica_pret()` pentru tipurile 23, 30, 41 — apel `actualizeaza_politica_pret` | Politica **curenta**, nu cea a documentului | Medie (tipuri de transfer) |
|
||||
| 6 | `ofacturare_comun.prg:240` | `.initializeaza_setari_document()` → `actualizeaza_document(tip)` → `id_fdoc` / `fdoc` | Reconfigureaza tipul de document din setarile curente | Medie |
|
||||
| 7 | `ofacturare_comun.prg:245-283` | ramura **ROACONTRACTE** (`goContract`): suprascrie `id_client`, `nume_client`, `cod_fiscal`, `listaid`, `descriere`, `id_sectie`, `id_responsabil`, `id_valuta`, **si `.datascad = .dataact + lnScadentaIncasare`**, **si `.afisare_scadenta`** | Daca `goContract` exista in sesiune, **suprascrie antetul documentului editat cu datele contractului** | **Mare**, conditionata de context |
|
||||
| 8 | `ofacturare_comun.prg:288-316` | ramura **ROACOMENZI** (`goComanda`, `tnTip = 3`): idem, `id_client`, `listaid`, `descriere`, `id_sectie`, `sectie` | Idem | **Mare**, conditionata |
|
||||
| 9 | `ferestre_cere_date.vc2:3177-3179` | `If This.opt_incasat.Value = 2 → poDate.incasat = poDate.totalctva` | Suprascrie suma incasata cu totalul documentului | Medie (grup C, blocat in etapa I) |
|
||||
| 10 | `ferestre_cere_date.vc2:3166-3171` | `poDate.id_casa = gnid_part_casa` (casa implicita a firmei) | Suprascrie casa documentului | Medie (grup C) |
|
||||
| 11 | **`ferestre_cere_date.vc2:3183-3185`** | `IF poDate.tip = 2 AND poDate.afisare_scadenta = 0 AND AT([Scadenta la ], poDate.text_aditional) = 0` → **prefixeaza** `text_aditional` cu `„Scadenta la N zile."` | **A doua instanta a exact aceluiasi defect ca lookup-ul delegatului**, si pe un camp din cei 14: modifica `text_aditional` la `Init`, fara actiune a utilizatorului | **Mare** |
|
||||
| 12 | `ferestre_cere_date.vc2:3150` | `If poDate.incasat <> 0 → Thisform.opt_incasat.Value = 2` — atribuire programatica pe `opt_incasat` | Declanseaza `ProgrammaticChange` → `actualizeaza_tipincasare()` → alocare/dezalocare de numere de chitanta (riscul deja documentat in `s3b_alte_date_analitice.md` §4.3) | Medie |
|
||||
| 13 | `ofacturare.prg:207-209` | `poGeneratorNumere.ResetNumere()` + `creeaza_cursor_serii(nIdTipDoc)`, la fiecare trecere prin `factureaza` | **Aloca un numar nou** pe calea de emitere | **Mare** — vezi 4.2 |
|
||||
| 14 | `ofacturare.prg:184-193` | `Do Case` pe `tnTip` care forteaza `poDate.nIdTipDoc` (5 = FACTURA / 6 = AVIZ) **inainte** de `completeaza_setari_document` | Pe editare, `nIdTipDoc` trebuie sa vina din document (proforma = 23, bon = 3), nu din tip | Medie |
|
||||
|
||||
**Nr. 11 merita subliniat**: garda lui (`AT([Scadenta la ], text_aditional) = 0`) il face inert pe un
|
||||
document de contract emis **cu acelasi numar de zile de scadenta**. Devine activ exact cand
|
||||
scadenta s-a schimbat de la emitere — adica precis cazul in care textul e cel vechi si trebuie
|
||||
pastrat. **Metoda `Destroy`/`Unload` face si operatia inversa** (`:3037-3038`: `Strtran(...,[])`),
|
||||
ceea ce inseamna ca pe calea normala prefixul e adaugat si scos in aceeasi sesiune; pe o cale de
|
||||
editare care **nu** trece prin acelasi `Unload`, prefixul ar ramane. **[D]** — n-am urmarit toate
|
||||
caile de iesire ale formularului.
|
||||
|
||||
### 2.3 Regula generala propusa
|
||||
|
||||
> **Orice camp al lui `poDate` populat prin apel Oracle sau dintr-o variabila globala/`go*` in
|
||||
> `Init`/`Load` e o sugestie pentru document nou, nu o valoare de document.** Pe calea de editare,
|
||||
> ordinea trebuie sa garanteze ca aceste sugestii **nu ajung sa se execute** (garda), nu doar ca sunt
|
||||
> **suprascrise dupa** (ceea ce ar merge pentru valori, dar **nu** pentru efectele secundare — nr. 12
|
||||
> si nr. 13 aloca numere, nu doar seteaza campuri).
|
||||
|
||||
---
|
||||
|
||||
## 3. Ce se blocheaza la editare, si de ce
|
||||
|
||||
### 3.1 Blocate — schimbarea lor ar insemna alt document
|
||||
|
||||
| Camp / control | De ce | Ruta care ar lipsi oricum |
|
||||
|---|---|---|
|
||||
| **Tipul documentului** (`Ct_clb_fdoc`, `poDate.tip` / `nIdTipDoc`) | Tipul decide ce sursa se consuma, ce nota se scrie si ce garzi se aplica. `do_copiaza` il **degradeaza** tocmai pentru ca un document copiat nu mai poate reconsuma sursa (`ofacturare_comun.vc2:3694-3711`) **[V]**; la editare degradarea e interzisa prin plan, deci tipul e fix. | grupul B — nicio ruta de scriere pe loc |
|
||||
| **Sursa** (`Ct_clb_altele`) | Vezi 3.2 | grupul B |
|
||||
| **Clientul** (`Ct_clb_nume_client`) | Schimbarea partenerului rescrie `IREG_PARTENERI`, `ACT`, soldurile — alt document | grupul B |
|
||||
| **Valuta, `zi_curs`, gestiunea sursa, politica de preturi** | grupul B, decizia 25; in plus `zi_curs` si `id_pol` **n-au valoare salvata** (1.5) | grupul B |
|
||||
| **Grupul C** (incasare) | decizia 25; in plus `opt_incasat` are efect secundar de alocare (2.2 nr. 12) | grupul C |
|
||||
| **Grupul A** (venit/cheltuiala, sectie, responsabil, lucrare) | decizia 26 — read-only permanent in #13; in plus doua din patru **nu exista pe `VANZARI`** (1.2.c) | editarea e a lui #6 |
|
||||
|
||||
### 3.2 `Ct_clb_altele` — verificarea ceruta explicit, cu doua corectii la plan [V]
|
||||
|
||||
Planul (`:2952-2955`) spune: *„eticheta schimbata pe tip de `do_schimba_explicatia` („Nr. contract" /
|
||||
„Nr. comanda" / „Nr. factura" / „Nr. facturi" / „Locatie", `ofacturare.vc2:9633-9643`), eliminat azi
|
||||
din formular cand `gnScadereStoc = 0` si tipul e 1/5/10 fara copiere."*
|
||||
|
||||
Citit ramura cu ramura din `frm_date_factura.Init` (`ofacturare.vc2:9563-9796`), `Do Case`-ul de la
|
||||
`:9615-9722`:
|
||||
|
||||
**Corectia 1 — lista de etichete e incompleta.** Intervalul `9633-9643` din plan e corect ca interval,
|
||||
dar contine **sase** apeluri, nu cinci: `Nr. contract` (`:9633`, tip 2/6/52), `Nr. comanda` (`:9635`,
|
||||
tip 3), **`Nr. aviz / avize` (`:9637`, tip 4)**, `Nr. factura` (`:9639`, tip 7), `Nr. facturi`
|
||||
(`:9641`, tip 8/9), `Locatie` (`:9643`, tip 45). **Planul omite exact tipul 4** — cel pentru care
|
||||
`Ct_clb_altele` tine **lista de avize**, adica singura sursa care trebuie refurnizata lui S9
|
||||
(sectiunea 6). Omisiunea nu e cosmetica.
|
||||
|
||||
**Corectia 2 — conditia de eliminare e mai larga decat cea din plan.** `ct_clb_altele` e scos
|
||||
(`RemoveObject`) in **trei** situatii, nu una:
|
||||
|
||||
| Ramura | Conditie | Ce face |
|
||||
|---|---|---|
|
||||
| `:9616-9628` | `gnScadereStoc = 0 And Inlist(poDate.tip, 1, 5, 10, **48, 49**)` | daca `llCopiere` → eticheta `Nr. factura` (`:9623`); altfel **`RemoveObject('ct_clb_altele')`** (`:9627`) |
|
||||
| `:9651-9660` | **`gnScadereStoc = 1`** `And Inlist(poDate.tip, 1, 5, 10)` | idem (`:9653` / `:9658`) |
|
||||
| `:9705-9712` (in `Otherwise`) | `Inlist(poDate.tip, 48, 49)` | **`RemoveObject('ct_clb_altele')`** neconditionat |
|
||||
|
||||
Deci: tipurile sunt **1, 5, 10, 48, 49** (nu doar 1/5/10), si eliminarea are loc si cand
|
||||
**`gnScadereStoc = 1`**, nu doar cand e `0`. Formularea din plan („`gnScadereStoc = 0` si tipul e
|
||||
1/5/10") descrie **una** din trei ramuri.
|
||||
|
||||
**Consecinta pentru S8, si e in favoarea noastra:** pe ramurile 1 si 2, `llCopiere = .T.` **pastreaza**
|
||||
controlul si ii pune eticheta `Nr. factura`. Calea de editare, care va avea acelasi semnal ridicat
|
||||
(`lCopiere` sau `lEditare`), **mosteneste automat pastrarea controlului** — nu e nimic de adaugat, doar
|
||||
de **blocat**. Pe ramura 3 (tipurile 48/49) controlul e scos neconditionat; **[N]** n-am stabilit daca
|
||||
tipurile 48/49 intra in perimetrul etapei II.
|
||||
|
||||
### 3.3 Ce ramane editabil
|
||||
|
||||
Cei 14 parametri, prin `but_modifica` (S8c), **plus** liniile (cantitati, preturi, discounturi,
|
||||
explicatii, adaugari/stergeri) si discountul de document — care declanseaza regenerarea. Nimic din
|
||||
sectiunea 3.1.
|
||||
|
||||
---
|
||||
|
||||
## 4. Ordinea reala de incarcare, si de ce conteaza
|
||||
|
||||
### 4.1 Secventa propusa
|
||||
|
||||
```
|
||||
0. S7 a validat deja documentul (garzi) si a stabilit id_vanzare.
|
||||
1. Citeste ANTETUL: SELECT ... FROM FACT_VFACTURI WHERE id_vanzare = :id
|
||||
2. Citeste LISTA SURSA (avize) inainte de orice altceva: SELECT ... FROM VANZARI_CORESP (§6)
|
||||
3. poDate = CreateObject("oDateFactura", 0, 0) <-- tnIdSet = 0 => Init NU ruleaza (§4.2)
|
||||
4. poDate.lEditare = .T. <-- semnalul, inainte de orice formular
|
||||
5. Populeaza poDate din antet: cei 14 + tip + eproforma + valuta/curs +
|
||||
discount_evidentiat + client + gestiune + sursa (cListaSursa / cListaSursaAvize)
|
||||
6. Populeaza thisform.ndiscfactron / ndiscfactval din VANZARI.DISCOUNT
|
||||
7. Citeste LINIILE (canalul din §1.1(B)) -> crsarticole
|
||||
8. GARDA DE RECALCUL: thisform.lIncarcare = .T.
|
||||
9. Umple crsfactura din crsarticole, fara dialoguri (§4.3)
|
||||
10. Adauga lista de preturi peste crsarticole (pentru S4g: adaugarea de articole noi)
|
||||
11. thisform.lIncarcare = .F.
|
||||
12. Un singur do_calculeaza_totaluri()
|
||||
13. SNAPSHOT pentru S8b (§5)
|
||||
14. Blocheaza antetul (S8c) si campurile din §3.1; arata formularul
|
||||
```
|
||||
|
||||
### 4.2 De ce pasul 3 arata asa — `tnIdSet = 0` [V]
|
||||
|
||||
Tot corpul lui `oDateFactura.Init` e inchis in `If !Empty(m.tnIdSet)` (`ofacturare_comun.prg:225`).
|
||||
Cu `tnIdSet = 0`, **niciuna dintre cele opt initializari de la 2.2 (nr. 1-8) nu se executa**, iar
|
||||
constructorul devine inert. **Precedentul exista si e chiar cel de reincarcare a unui document:**
|
||||
`relisteaza_ofacturare_stoc` face `poDate = Createobject("oDateFactura", 0, 0)`
|
||||
(`ofacturare_stoc.prg:497`) **[V]** exact din acest motiv.
|
||||
|
||||
Asta rezolva **printr-o singura decizie de constructie** opt din cele paisprezece initializari
|
||||
periculoase, fara nicio modificare in `oDateFactura` — si e mult mai robust decat „suprascriem dupa",
|
||||
pentru ca nu depinde de completitudinea listei de suprascrieri.
|
||||
|
||||
**Ce ramane de tratat separat, pentru ca nu trece prin `Init`:**
|
||||
- nr. 9-12 (`frm_alte_date.Init`) → garda `poDate.lEditare` de la 2.1;
|
||||
- **nr. 13, `poGeneratorNumere`** — e apelat din `factureaza` (`ofacturare.prg:207-209`), nu din
|
||||
`Init`. Pe calea de editare **nu trebuie sa se aloce numar**: seria si numarul vin din document
|
||||
(decizia F / plan §F). `oGeneratorNumere` are deja `dezaloca_numar` si `verifica_numar`
|
||||
(`ofacturare.prg:227`, `:250`), dar calea corecta e **sa nu se aloce deloc**. **[N]** —
|
||||
`COMUN\programe\oserii_numere.prg` n-a fost citit in aceasta sesiune; planul citeaza `:227-233`
|
||||
pentru afirmatia „la modificare nu se aloca numar nou". **De verificat la implementare** ca ocolirea
|
||||
alocarii nu lasa `poDate.rezultat_serii` intr-o stare pe care `frm_date_factura.Init` o citeste
|
||||
(`ofacturare.vc2:9736`: `If Inlist(poDate.rezultat_serii, 1, 2, 3) → clb_serie_act.do_initializeaza(...)`).
|
||||
|
||||
### 4.3 Pasul 9 e piesa care lipseste azi — si e cea mai mare
|
||||
|
||||
**[V]** Pe calea de copiere, `crsfactura` ramane gol; documentul se compune manual sau prin
|
||||
`do_adauga_tot`. `do_adauga_tot` (`ofacturare.vc2:13169-13198`) face `SCAN` peste `crsarticole` si
|
||||
cheama `do_adauga_articol(.T.)`, care (`:12871-12894`):
|
||||
|
||||
- pe ramura `poArticol.gestionabil = 0 Or gnScadereStoc = 0 Or poDate.tip = 45` — creeaza
|
||||
`frm_articol_factura` **fara sa-l arate** (cu `tlImplicit = .T.`) si calculeaza totalurile. Acceptabil.
|
||||
- pe ramura `Otherwise` — cheama **`do_alege_stoc(...)`**, adica **dialogul de alegere din stocul de
|
||||
azi**. Pe calea de editare asta e gresit de doua ori: (a) ar putea cere interventia utilizatorului
|
||||
pentru fiecare linie gestionabila la simpla deschidere a documentului; (b) ar alege **loturi/serii
|
||||
din stocul curent**, desi documentul are deja `LOT`, `SERIE` si `ID_GESTIUNE` proprii, intoarse de
|
||||
cursor.
|
||||
|
||||
In plus, `do_adauga_tot` are un `Do While` cu `aMessageBox("Nu ati selectat toata cantitatea …")`
|
||||
(`:13188`) — **un modal per linie neacoperita**. Inacceptabil la incarcare.
|
||||
|
||||
Si `do_adauga_articol` scrie `Replace id_temp With Recno()` (`:12951` si `:13000`) **[V]** — deci chiar
|
||||
daca sursa ar avea `ID_VANZARE_DET`, **nu ar ajunge in `crsfactura` pe aceasta cale**.
|
||||
|
||||
**Concluzie:** S8 nu poate refolosi `do_adauga_tot`. Ii trebuie o **populare directa**
|
||||
`crsarticole → crsfactura` (un `INSERT INTO crsfactura ... SELECT ...` plus recalculul valorilor pe
|
||||
linie prin `calculeaza_totaluri`), care:
|
||||
1. **nu deschide niciun dialog**;
|
||||
2. pastreaza `lot`, `serie`, `id_gestiune`, `id_pol`, `pretd` **din document**;
|
||||
3. duce `id_vanzare_det` intr-o coloana proprie noua pe `crsfactura` (**nu** in `id_temp`, care e
|
||||
suprasolicitat: `Recno()` pe o cale, `id_vanzare_det` pe alta — `prelucreaza_facturacrs` face
|
||||
`id_vanzare_det As id_temp`, `ofacturare_comun.prg:1811` **[V]**);
|
||||
4. duce `taxcode`.
|
||||
Precedentul de structura exista: blocul `tnTip = 30` din `factureaza`
|
||||
(`ofacturare.prg:340-378`) face exact o populare directa `crsarticole → crsfactura` prin
|
||||
`INSERT INTO crsfactura (...) Values (...)`, fara niciun dialog **[V]**. Se cloneaza forma lui.
|
||||
|
||||
### 4.4 Garda de „nu recalcula acum" — unde, si de ce
|
||||
|
||||
`crsfactura` e `RecordSource`-ul gridului, iar coloanele au `Valid`/`InteractiveChange` care
|
||||
recalculeaza. La populare programatica, `Valid` **nu** se declanseaza in VFP (se declanseaza doar la
|
||||
iesirea din control, pe interactiune) — deci riscul principal **nu** e in grid, ci in:
|
||||
|
||||
- **`ControlSource` legate direct de `poDate`** (ex. `poDate.discount_evidentiat`,
|
||||
`ofacturare.vc2:11305`) — atribuirea programatica declanseaza `ProgrammaticChange`, nu `Valid`
|
||||
**[D]** (comportament VFP standard; nu l-am observat rulat aici);
|
||||
- **`opt_incasat.Value =`** — 2.2 nr. 12, efectul secundar documentat;
|
||||
- **`clb_discount.procent.Value =`** (`ofacturare.vc2:13475`) — daca S8 populeaza procentul de discount
|
||||
in loc de suma, `InteractiveChange` ar recalcula discountul pe baza curenta, care in timpul popularii
|
||||
e incompleta.
|
||||
|
||||
**Recomandare concreta:** un singur flag pe formular, `Thisform.lIncarcare`, testat la intrarea in
|
||||
`do_calculeaza_totaluri` si in `actualizeaza_discount` (`ofacturare.vc2:13408-13415`), plus **regula ca
|
||||
discountul de document se incarca ca SUMA** (`ndiscfactron`/`ndiscfactval`, valorile pe care le citeste
|
||||
si scrierea, `:14272-14274`), **nu ca procent** — procentul se recalculeaza din suma la pasul 12
|
||||
(`:13475`: `procent.Value = Round(ndiscfactron * 100 / nbazafdiscount, 2)`), nu invers.
|
||||
|
||||
### 4.5 Asezarea — decizia 5
|
||||
|
||||
Nimic din cele de mai sus nu schimba aspectul: nu se adauga banda de avertizare, coloane cu valori
|
||||
initiale sau panou de diferente. Se schimba **titlul ferestrei** si **butonul principal** (decizia 9 /
|
||||
S8c). Diferentele vizibile fata de introducere sunt **doar** cele care rezulta din blocarea campurilor
|
||||
(3.1) si din ascunderea lui `zi_curs` (1.5) — ultima e o abatere minora de la „identic", propusa
|
||||
motivat, **de confirmat de Marius** (intrebarea 4, sectiunea 8).
|
||||
|
||||
---
|
||||
|
||||
## 5. Interfata cu S8b — ce se retine ca „stare initiala"
|
||||
|
||||
### 5.1 Momentul
|
||||
|
||||
Snapshot-ul se ia la **pasul 13**: dupa incarcarea completa si dupa singurul recalcul, dar **inainte de
|
||||
afisarea formularului**. Nu mai devreme (valorile derivate n-ar fi calculate) si nu mai tarziu (orice
|
||||
`Init` de subformular ar putea polua — 2.2).
|
||||
|
||||
### 5.2 Ce se retine, si in ce forma
|
||||
|
||||
| Grup | Forma | Normalizare obligatorie inainte de comparatie |
|
||||
|---|---|---|
|
||||
| **Cei 14 parametri** | copie a valorilor din `poDate` (+ `id_ruta`, 1.5) intr-un obiect `poDateInitial` | `.NULL.` vs `0` vs `''` — functie unica aplicata **simetric**; `text_aditional`: **`LEFT(...,100)` + `Chr(170)`→`CR+LF`** pe ambele parti (1.7) |
|
||||
| **Discountul de document** | `ndiscfactron` si `ndiscfactval`, **ca sume** | `Round(..., gnPc)` / `Round(..., gnPVal)` |
|
||||
| `discount_evidentiat` | scalar | — |
|
||||
| **Liniile** | copie a lui `crsfactura`, cheia = **`id_vanzare_det`** (coloana noua, 4.3) | `Round` la `gnPc` / `gnPPretV` / `gnPCant`; pretul comparat in forma **rotunjita + `DIFERENTA`** (1.4.ii) |
|
||||
| **Explicatie / taxcode pe linie** | in aceeasi copie | `Alltrim`, `Nvl(...,'')` |
|
||||
|
||||
**Trei precizari care corecteaza sau completeaza S8b:**
|
||||
|
||||
1. **Cheia `id_vanzare_det` nu vine gratuit** — S8b o presupunea incarcata („coloana deja incarcata la
|
||||
S8"). Nu e (1.1). Devine **livrabil al lui S8**, nu premisa.
|
||||
2. **Liniile fara `id_vanzare_det`** (adaugate in sesiune) se marcheaza cu `0`/`.NULL.` si inseamna
|
||||
direct „regenerare", ca in S8b §1.3.
|
||||
3. **`zi_curs` se scoate din comparatie** (1.5) — altfel orice document ar aparea modificat.
|
||||
|
||||
### 5.3 Cele 7 campuri comune `scrie_factura2` / `modifica_date_factura`
|
||||
|
||||
Nimic de adaugat la S8b §3.1 — lantul cu prioritate ramane. S8 doar garanteaza premisa lui: **`poDate`
|
||||
contine, la momentul confirmarii, valorile documentului plus editarile utilizatorului si nimic altceva**
|
||||
— ceea ce e exact ce asigura sectiunile 2 si 4.
|
||||
|
||||
---
|
||||
|
||||
## 6. Interfata cu S9 — ce trebuie sa incarce S8 ca S9 sa poata refurniza lista sursa
|
||||
|
||||
Pasul 1 nou al lui S9 (handoff, rezultatul 7) cere **citirea listei sursa inainte de stergere**. S8 e
|
||||
locul unde se citeste, pentru ca **dupa stergere `VANZARI_CORESP` are `STERS = 1`** (`:5582` **[V]**) si
|
||||
`VANZARI.ID_COMANDA` / `ID_CTR` raman pe randul soft-sters.
|
||||
|
||||
**Ce incarca S8, si de unde:**
|
||||
|
||||
| Ce | Camp propus pe `poDate` | Sursa | Consumator la reemitere |
|
||||
|---|---|---|---|
|
||||
| lista de avize | `cListaSursaAvize` | `SELECT ID_VANZARE_AVIZ FROM VANZARI_CORESP WHERE ID_VANZARE_FACT = :id AND STERS = 0 AND TIP = 1` | `pack_facturare.clistaid_avize` → `scrie_corespondente_vanzari(1)` si `marcheaza_facturat` |
|
||||
| comanda | `cListaSursa` | `FACT_VFACTURI.ID_COMANDA` | `pack_facturare.clistaid` |
|
||||
| contract | `cListaSursa` (+ `id_ctr`) | `FACT_VFACTURI.ID_CTR` | idem, plus `CTR_RATE_FACTURI` |
|
||||
| tipul original | `poDate.tip`, nedegradat | `FACT_VFACTURI.TIP` | `CASE`-ul pe `ntip` din `finalizeaza_factura` |
|
||||
|
||||
**Doua avertismente:**
|
||||
|
||||
1. **`poDate.listaid` NU poate purta aceasta informatie** — la incarcare el trebuie sa fie
|
||||
`id_vanzare`-ul documentului insusi, ca sa se citeasca liniile (1.6). Doua campuri distincte,
|
||||
nu unul reinterpretat.
|
||||
2. **Semantica valorilor lui `TIP`** din `VANZARI_CORESP` **nu e stabilita aici [N]** (1.6) — se
|
||||
citeste din `CASE`-ul real al lui `finalizeaza_factura` la implementare.
|
||||
|
||||
**Nu tine de S8**, dar se semnaleaza pentru ca S8 decide continutul lui `crsarticole`:
|
||||
`do_scrie_factura` face `Select crsarticole` + `Calculate Sum(cantitate) To lnCantitateRamasa` pe
|
||||
ramurile `poDate.Tip = 4` (`ofacturare.vc2:14305-14307`) si `Inlist(poDate.Tip,3,21,25,28,42,47)`
|
||||
(`:14336-14338`) **[V]**, si pe baza sumei intreaba *„Doriti sa se inregistreze si avizul de retur?"* /
|
||||
*„Doriti sa se inchida comanda?"* si seteaza `pnParametruAditional`. Pe calea de editare `crsarticole`
|
||||
contine liniile documentului **plus lista de preturi adaugata peste ele** (`ofacturare.prg:465-473`
|
||||
**[V]**), deci **suma e lipsita de sens** si intrebarea ar aparea gresit, cu efect real pe
|
||||
`pnParametruAditional`. **De rezolvat in S9** — fie prin cursor separat pentru lista de preturi, fie
|
||||
prin calcularea sumei doar peste randurile provenite din document.
|
||||
|
||||
---
|
||||
|
||||
## 7. Criteriul „gata cand", in forma testabila
|
||||
|
||||
**Comun tuturor tipurilor.** Pe un document existent, dupa deschidere si **inainte** de orice
|
||||
interactiune:
|
||||
|
||||
| # | Ce se verifica | Cum |
|
||||
|---|---|---|
|
||||
| C1 | serie, numar, data, scadenta afisate = cele din `VANZARI` | comparatie ecran ↔ `SELECT serie_act, numar_act, data_act, data_scad FROM vanzari WHERE id_vanzare = :id` |
|
||||
| C2 | **niciun numar nou alocat** | inainte/dupa: ultimul numar din generatorul de serii pentru `nIdTipDoc` e neschimbat |
|
||||
| C3 | numarul de linii din grid = `SELECT count(*) FROM vanzari_detalii WHERE id_vanzare = :id AND sters = 0` | |
|
||||
| C4 | pe fiecare linie: cantitate, pret, discount, gestiune, lot, serie, cota TVA, explicatie, `taxcode` = valorile din `VANZARI_DETALII` (pret in forma rotunjita + `DIFERENTA`, 1.4.ii) | |
|
||||
| C5 | totalurile afisate = `VANZARI.TOTAL_FARA_TVA` / `TOTAL_TVA` / `TOTAL_CU_TVA` | |
|
||||
| C6 | discountul de document si `discount_evidentiat` = `VANZARI.DISCOUNT` / `DISCOUNT_EVIDENTIAT` | |
|
||||
| C7 | delegat, masina, agent, adresa de facturare, `dataora_exp`, `text_aditional`, `listare_detaliata`, `tip_saft`, `efactura`, `id_ruta` = `VANZARI` | inclusiv **cazul „documentul n-are delegat"** → campul ramane gol (2.1) |
|
||||
| C8 | **nicio scriere in baza** — criteriul de baza al lui S8b | `SELECT dataoras, id_utils FROM vanzari WHERE id_vanzare = :id` neschimbat dupa deschidere+inchidere; idem pe `VANZARI_DETALII` |
|
||||
| C9 | **niciun dialog modal** la deschidere (nici alegere de stoc, nici „nu ati selectat toata cantitatea", nici „doriti sa inchideti comanda") | observatie |
|
||||
| C10 | aspectul e cel de introducere (decizia 5): fara banda, fara coloane de valori initiale, fara panou de diferente; difera doar titlul si butonul principal | observatie |
|
||||
|
||||
**Pe fiecare tip de sursa, in plus:**
|
||||
|
||||
| Tip | Ce se verifica specific |
|
||||
|---|---|
|
||||
| **comanda** (3, 21, 25, 28, 42, 47) | `Ct_clb_altele` afiseaza numarul comenzii, eticheta „Nr. comanda", **blocat**; `poDate.cListaSursa` = `VANZARI.ID_COMANDA` |
|
||||
| **contract** (2, 6, 26, 52) | eticheta „Nr. contract", blocat; `id_ctr` incarcat; **`goContract` din sesiune NU suprascrie antetul** (2.2 nr. 7); daca S10 se implementeaza, **niciun avertisment de re-derivare la simpla deschidere** (`cursor_retur_document` citeste pretul stocat, rezultatul 6 al handoff-ului) |
|
||||
| **aviz** (tip 4 = factura din avize) | eticheta **„Nr. aviz / avize"** (3.2), blocat; `poDate.cListaSursaAvize` = randurile `VANZARI_CORESP` ale documentului; **nicio intrebare „doriti sa se inregistreze si avizul de retur?"** la deschidere (§6) |
|
||||
| **factura simpla** (1, 5, 10) | `Ct_clb_altele` **pastrat si vizibil** cu eticheta „Nr. factura" (3.2, ramurile 1 si 2 cu `llCopiere`), spre deosebire de emiterea normala unde e scos |
|
||||
| **retur** (8, 9, 24) | `zi_curs` e oricum scos azi pentru 8/9 (`ofacturare.vc2:9720-9725`) — comportament neschimbat; liniile se incarca cu cantitatile documentului, nu cu cele disponibile la retur |
|
||||
| **ROAAUTO** | `poDate.id_ordl` incarcat din `VANZARI.ID_ORDL`; **[N]** nu s-a verificat in aceasta sesiune ce alte campuri specifice ROAAUTO exista pe antet — `roaauto_facturi.md` nu a fost citit |
|
||||
| **proforma** (`eproforma = 1`) | `poDate.eProforma` incarcat (1.2.a) → `frm_alte_date.Init` sare din start pe ramura de proforma (`:3110`), iar `gestionabil` vine `0` din cursor (1.4) |
|
||||
|
||||
---
|
||||
|
||||
## 8. Riscuri, goluri ramase, si intrebarile pentru Marius
|
||||
|
||||
### 8.1 Ce n-am putut stabili, si de ce
|
||||
|
||||
| # | Ce | De ce |
|
||||
|---|---|---|
|
||||
| N1 | Semantica exacta a valorilor `VANZARI_CORESP.TIP` (`1/2/3`, `4` liber) | Cere citirea `CASE`-ului pe `ntip` din `finalizeaza_factura`, in afara perimetrului parcurs. **Nu se preia din raportul precedent** — e exact tiparul cifrei gresite din runda 13. |
|
||||
| N2 | Daca ocolirea alocarii de numar lasa `poDate.rezultat_serii` intr-o stare pe care `frm_date_factura.Init` o citeste gresit (`ofacturare.vc2:9736`) | `COMUN\programe\oserii_numere.prg` necitit in aceasta sesiune |
|
||||
| N3 | Daca prefixul „Scadenta la N zile." adaugat la `Init` (2.2 nr. 11) e scos pe **toate** caile de iesire | Am vazut operatia inversa la `ferestre_cere_date.vc2:3037-3038`, dar n-am urmarit toate caile de `Unload`/`Destroy` |
|
||||
| N4 | Campurile de antet specifice **ROAAUTO** | `roaauto_facturi.md` necitit |
|
||||
| N5 | Daca tipurile **48/49** (custodie) intra in perimetrul etapei II | Conteaza pentru 3.2, ramura 3 |
|
||||
| N6 | Daca `frm_facturare_articole2` (varianta paralela) trebuie tratata identic | Are `do_adauga_tot` / `do_adauga_articol` proprii (`ofacturare.vc2:17476`, `:17124`), cu logica usor diferita (fara testul `llGestionabil`) — **de decis daca S8 tinteste ambele forme sau doar `frm_facturare_articole`** |
|
||||
|
||||
### 8.2 Intrebari pentru Marius, cu recomandare
|
||||
|
||||
1. **Canalul de citire a liniilor: (A) extindem `cursor_retur_document`, (B) procedura noua
|
||||
`cursor_editare_document`, sau (C) `FACT_VFACTURI_DETALII`?** (1.1)
|
||||
*Recomandare:* **(B)**. Argumentul nu e estetica, ci ca (A) schimba **comportamentul copierii**
|
||||
(`taxcode` ar incepe sa se propage), iar (C) pierde `ID_POL`, `PRETD` si tratamentul valutar —
|
||||
partea grea. (B) da si libertatea de a alege corect `GESTIONABIL` (1.4.i).
|
||||
|
||||
2. **`GESTIONABIL` la editare: din nomenclatorul de azi (`IN_STOC`) sau din document (`ID_GESTIUNE`)?**
|
||||
(1.4.i)
|
||||
*Recomandare:* **din document**. Un document editat trebuie sa arate cum a fost emis; regimul de
|
||||
stoc al articolului s-a putut schimba intre timp fara nicio legatura cu documentul.
|
||||
|
||||
3. **`text_aditional`: se normalizeaza la incarcare (`Chr(170)` → `CR+LF`) sau se afiseaza ca atare?**
|
||||
(1.7)
|
||||
*Recomandare:* **se normalizeaza la incarcare si se re-normalizeaza la comparatie**, altfel orice
|
||||
document cu text pe mai multe randuri apare modificat la deschidere. Nota: cele doua rute de scriere
|
||||
salveaza **forme diferite** ale campului (truncat/substituit vs. brut) — merita semnalat separat,
|
||||
e un defect preexistent, nu al lui #13.
|
||||
|
||||
4. **`zi_curs` pe calea de editare: ascuns, sau afisat gol?** (1.5)
|
||||
*Recomandare:* **ascuns**, cu precedent in acelasi `Do Case` (tipurile 8/9). E o abatere mica de la
|
||||
decizia 5, si o semnalez ca atare — un camp afisat gol pe un document care are curs ar fi mai
|
||||
derutant decat absenta lui.
|
||||
|
||||
5. **`poDate.lEditare` ca proprietate pe `oDateFactura`, sau parametru pe `frm_alte_date.Init`?** (2.1)
|
||||
*Recomandare:* **proprietate**, pentru ca sunt **cel putin patru** locuri care au nevoie de semnal
|
||||
(2.2 nr. 9-13), nu unul, si pentru ca `lCopiere` e deja acolo, cu exact acelasi rol.
|
||||
|
||||
6. **`id_ruta` — proprietate noua pe `oDateFactura`?** (1.5)
|
||||
*Recomandare:* **da**. Fara ea S8c nu poate implementa unul din cei 14 parametri, iar azi valoarea
|
||||
circula doar prin `poRec`, care nu exista in formularul unificat.
|
||||
|
||||
7. **Tipurile 48/49 si `frm_facturare_articole2`** (N5, N6) — intra in perimetrul etapei II?
|
||||
*Recomandare:* **nu acum**; se declara explicit ca neacoperite, ca sa nu se descopere la testare.
|
||||
|
||||
8. **Defectul de la 2.2 nr. 11** (prefixarea `text_aditional` la `Init` pentru contracte) — se repara
|
||||
in #13, se semnaleaza separat, sau se lasa?
|
||||
*Recomandare:* **se ocoleste in #13** (prin `lEditare`) si **se semnaleaza separat** ca defect
|
||||
preexistent — repararea lui pe calea de emitere nu tine de aceasta poveste.
|
||||
|
||||
### 8.3 Ce a fost corectat fata de materialele existente
|
||||
|
||||
| Afirmatie anterioara | Stare |
|
||||
|---|---|
|
||||
| S8b: „lookup-ul delegatului trebuie sarit explicit, altfel pica primul criteriu" | **Nuantat** — garda `Empty(id_delegat) And Empty(id_masina)` exista deja (`ferestre_cere_date.vc2:3111`); riscul e real **doar** pe documentele fara delegat si fara masina |
|
||||
| S8b §1.2 / §7.4: „cheia de linie `id_vanzare_det`, coloana deja incarcata la S8" | **Infirmat** — `cursor_retur_document` nu o intoarce; devine livrabil al lui S8 |
|
||||
| S8b §5.2: „S8 trebuie sa suprascrie implicitul `zi_curs` cu data reala salvata" | **Infirmat structural** — nu exista coloana `ZI_CURS` pe `VANZARI` |
|
||||
| Plan S8: etichetele lui `Ct_clb_altele` (cinci) | **Incomplet** — sunt sase; lipseste „Nr. aviz / avize" (tip 4), exact cazul relevant pentru S9 |
|
||||
| Plan S8: „eliminat cand `gnScadereStoc = 0` si tipul e 1/5/10 fara copiere" | **Incomplet** — trei ramuri, tipurile `1,5,10,48,49`, si pentru `gnScadereStoc = 1`, nu doar `0` |
|
||||
| Plan S8: „`cursor_retur_document(V_COPIERE=1)` pentru linii" | **Insuficient** — umple selectorul, nu documentul; lipsesc `ID_VANZARE_DET` si `TAXCODE` |
|
||||
| Plan S8: „`completeaza_setari_document` pentru antet" | **Insuficient** — 16 proprietati din ~120, niciuna de identitate |
|
||||
|
||||
---
|
||||
|
||||
*Cercetare incheiata pe toate cele 8 puncte cerute in briefing. Afirmatiile portante au `fisier:linie`
|
||||
sau nume de coloana din dictionar. Nu s-a modificat niciun fisier de cod; pe Oracle numai `SELECT` pe
|
||||
`all_tab_columns`.*
|
||||
311
docs/cercetare/s8b_rutarea_scrierii.md
Normal file
311
docs/cercetare/s8b_rutarea_scrierii.md
Normal file
@@ -0,0 +1,311 @@
|
||||
# S8b — Proiectare: rutarea scrierii dupa ce utilizatorul a schimbat ceva
|
||||
|
||||
Livrabilul povestii **S8b** din `docs\plan_13_unificare_formular_facturare.md:2890-2898` (G-bis,
|
||||
deciziile 6/9/25/26). Cercetare READ-ONLY — zero editari de cod, zero `git_sync.ps1`/`txt2vcx.ps1`,
|
||||
zero commit, zero scriere Oracle (doar SELECT, nefolosit efectiv — tot ce trebuia era in codul VFP/
|
||||
PL-SQL deja exportat sau in rapoartele de referinta citate in briefing).
|
||||
`COMUN\clase\ofacturare_comun.vc2` si `COMUN\programe\ofacturare_editare.prg` (perimetrul #6) doar
|
||||
citite, neatinse.
|
||||
|
||||
## Verdict (rezumat, 8 randuri)
|
||||
|
||||
Reteta din G-bis e corecta ca directie, dar are un gol nedocumentat pana acum: **`modifica_date_factura`
|
||||
nu e singura ruta care scrie cei 14 parametri de antet** — 7 din 14 (`id_delegat`, `id_masina`,
|
||||
`id_facturare`, `listare_detaliata`, `dataora_exp`, `id_agent`, `text_aditional`) sunt scrisi **si**
|
||||
de calea normala de emitere (`scrie_factura2`, apelata direct de regenerare, decizia 35), pentru ca
|
||||
ambele cai citesc din **acelasi obiect `poDate`**, nu din doua reprezentari separate (sectiunea 3,
|
||||
dovada pe cod). Consecinta directa pentru punctul cel mai periculos al sarcinii (schimbare simultana
|
||||
antet+sume): **cand regenerarea porneste, NU se mai cheama `modifica_date_factura` in plus** — ar fi
|
||||
fie redundant (pe cele 7 campuri comune), fie periculos (ar scrie pe un `ID_VANZARE` care tocmai a
|
||||
fost soft-sters sau inca nu exista). Regenerarea *este* deja calea de scriere a antetului cand sumele
|
||||
se schimba, nu o cale separata care trebuie compusa cu ea. Explicatia de linie e acoperita direct de
|
||||
`adauga_articol_factura` (parametru `V_EXPLICATIE`, plus `V_TAXCODE`), deci regenerarea o transporta
|
||||
fara cod suplimentar — dar **doar daca cursorul de linii incarcat la S8 e re-citit din formular, nu
|
||||
din snapshot-ul initial**. Cel mai probabil loc de fals-pozitiv pentru „deschid si inchid fara sa
|
||||
modific nimic” e lookup-ul „ultimul delegat/masina al clientului” din `frm_alte_date.Init`
|
||||
(sectiunea 5) — cod construit pentru emiterea unui document nou, periculos daca ruleaza neschimbat pe
|
||||
calea de editare.
|
||||
|
||||
---
|
||||
|
||||
## 1. Mecanismul de detectare a schimbarii
|
||||
|
||||
### 1.1 Optiuni si ce se strica la fiecare
|
||||
|
||||
| Optiune | Ce se strica |
|
||||
|---|---|
|
||||
| **Hash/checksum pe randuri** | Ascunde exact tipul de fals-pozitiv cel mai periculos aici: doua reprezentari numeric-egale dar text-diferite (`Str(12.5,18,2)` vs `Str(12.50,18,2)`, `.NULL.` vs `0` vs `""`) produc hash-uri diferite desi valoarea „reala" e identica. Orice normalizare facuta *inainte* de hash trebuie sa fie deja perfecta — hash-ul nu adauga nimic, doar ascunde bug-urile de normalizare in loc sa le arate. |
|
||||
| **Flag-uri `lModificat` in evenimentele de editare** | E robust doar daca *fiecare* eveniment care poate schimba o valoare seteaza flag-ul — un `ControlSource` legat direct (binding automat VFP, cazul majoritatii campurilor de antet aici, vezi `modifica_date_factura_parametri.md` §4) nu trece printr-un eveniment scriptat, deci flag-ul ramane `.F.` desi valoarea s-a schimbat. Whitelist fragil: la fiecare control nou adaugat, cineva trebuie sa-si aminteasca sa cablasje flag-ul. Cel mai riscant pentru campurile din `frm_alte_date` (S3b), unde `ControlSource=poDate.xxx` e tiparul dominant. |
|
||||
| **Snapshot la incarcare + comparatie camp cu camp la confirmare** (recomandat) | Cere disciplina la normalizare (precizie, `.NULL.`), dar e singura optiune care **nu poate rata o schimbare structurala** — compara starea finala, nu istoricul de evenimente, deci un binding automat care a schimbat o valoare fara eveniment scriptat tot apare in diff. E si optiunea deja folosita implicit in codebase pentru un caz inrudit: `chkSerieAct.Enabled = !EMPTY(NVL(poRec.numar_act,0))` (`ofacturare_comun.vc2:5736`) testeaza starea curenta, nu un istoric de evenimente. |
|
||||
|
||||
**Recomandare: snapshot + comparatie la confirmare**, din motivul de mai sus (robustete la binding
|
||||
automat) — argumentat, nu doar preferat.
|
||||
|
||||
### 1.2 Ce se snapshoteaza, concret
|
||||
|
||||
- **Antet**: o copie profunda a lui `poDate` (sau a subsetului de proprietati relevante) luata
|
||||
imediat dupa S8 (incarcare), **inainte** de orice lookup auto-completat (vezi sectiunea 5 — ordinea
|
||||
conteaza: daca lookup-ul „ultimul delegat" ruleaza inainte de snapshot, snapshot-ul insusi e deja
|
||||
poluat, si orice comparatie ulterioara devine inutila).
|
||||
- **Linii**: o copie a cursorului `crsfactura` (nume alias de confirmat la implementare — S4d il
|
||||
citeaza ca atare) imediat dupa populare la S8, cu cheia de linie **`id_vanzare_det`** (coloana deja
|
||||
incarcata la S8 din `VANZARI_DETALII`, cf. plan S8: „explicatia si taxcode pe fiecare linie" — nu
|
||||
exista alt candidat de cheie stabila in materialele citite; **de confirmat exact numele coloanei in
|
||||
cursor la implementare**, nu presupus mai departe aici).
|
||||
- **Discountul de document** (`VANZARI.DISCOUNT`, `discount_evidentiat`) — campuri de antet cu efect
|
||||
de suma, tratate separat de cele 14 (vezi matricea, sectiunea 2).
|
||||
|
||||
### 1.3 Capcanele reale de VFP, cu tratament explicit
|
||||
|
||||
| Capcana | Tratament recomandat |
|
||||
|---|---|
|
||||
| `.NULL.` vs `0` vs `""` | Comparatie prin functie de normalizare unica (`NVL`-style) aplicata **simetric** pe ambele parti (snapshot si curent) inainte de `=`, nu doar pe una — o comparatie `Nvl(nou,0) == vechi` fara acelasi tratament pe `vechi` e asimetrica si poate rata cazul `vechi=.NULL., nou=0` ca "neschimbat" cand de fapt utilizatorul a introdus explicit un zero peste un camp gol (relevant pt. campuri ca `id_ruta`/`id_agent`, unde `.NULL.` si `0` pot avea semnatura diferita in Oracle — `modifica_date_factura` scrie orice i se da, inclusiv `NULL` neconditionat, deci diferenta chiar conteaza acolo). |
|
||||
| Precizie numerica (`gnPc`, `gnPPretV`, `gnPCant`) | Comparatia nu se face pe valoarea `Double` bruta, ci pe reprezentarea **rotunjita la aceeasi precizie folosita la scriere** — acelasi `Round(x, gnPc)`/`Round(x, gnPPretV)`/`Round(x, gnPCant)` aplicat pe ambele parti inainte de `=`. Motiv concret: `adauga_articol_factura` primeste pretul ca `Str(..., 18, gnPc)` (text), deci orice zgomot de reprezentare in binar (ex. `12.4999999999` vs `12.5`) care nu apare si in textul trimis Oracle-ului nu trebuie sa declanseze regenerare — regenerarea trebuie sa porneasca de la o diferenta **care ar produce efectiv un text diferit trimis la Oracle**, nu de la zgomot de virgula mobila intern VFP. |
|
||||
| Randuri sterse/adaugate, nu doar modificate | Comparatie pe **multimea cheilor** `id_vanzare_det`, nu pe pozitie: chei prezente in snapshot dar absente in curent = sters; chei prezente in curent dar absente in snapshot (sau `id_vanzare_det` gol/`0`, sentinela de linie noua) = adaugat; chei prezente in ambele = potential modificat, comparat camp cu camp. **Orice** rand adaugat sau sters, indiferent de continutul lui, marcheaza direct pentru regenerare (tabelul din G-bis: „linii adaugate/sterse” → regenerare) — nu are sens sa se compare campuri pe un rand care oricum nu exista pe ambele parti. |
|
||||
| Ordinea randurilor | **Nu conteaza pentru detectie** — comparatia e pe chei (set), nu pe pozitie in grid. Ordinea ar conta doar daca reordonarea insasi ar fi o schimbare semnificativa pentru Oracle (nu e cazul — `adauga_articol_factura` nu are parametru de ordine vizibil in semnatura citata in `s10_pret_rederivat.md` §1). **Rezerva**: daca formularul unificat permite reordonare manuala a liniilor si aceasta conteaza pentru vreun raport (necercetat aici), ar trebui un camp explicit de ordine comparat separat — nu presupus din pozitia in cursor. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Matricea „ce s-a schimbat → ce ruta", exhaustiva
|
||||
|
||||
Sursa de adevar: G-bis (`plan:812-889`) + `modifica_date_factura_parametri.md` + `rute_scriere_antet.md`.
|
||||
|
||||
| Camp / grup | Ruta | De ce nu una mai ieftina |
|
||||
|---|---|---|
|
||||
| **Cei 14 parametri** (serie, numar, data, scadenta, ruta, delegat, masina, agent, `dataora_exp`, adresa facturare, text aditional, `listare_detaliata`, `tip_saft`, `efactura`) | `modifica_date_factura`, **daca nimic altceva nu s-a schimbat in aceeasi sesiune** | E singura cale directa pe `VANZARI`+`DOCUMENTE`+`ACT`+`IREG_PARTENERI`+`JV2007`+`RUL` care nu atinge sumele — mai ieftina decat regenerarea (fara stergere+reemitere, fara stoc, fara nota noua). Regenerarea ar face acelasi lucru, dar cu cost mult mai mare (tranzactie stergere+reemitere, stoc, ID_VANZARE nou) pentru un camp care nu atinge nicio suma — nu se justifica. |
|
||||
| **Explicatia si `taxcode` pe o linie**, **fara nicio alta schimbare pe linii** | `modifica_explicatie_articol` | Update pe doua coloane, fara sume — regenerarea ar fi disproportionata (stergere+reemitere completa pentru un text). |
|
||||
| **Cantitati, preturi, discount pe linie, gestiune, cota TVA, serie/lot** | Regenerare | Nu exista alta ruta — verificat exhaustiv, nicio procedura Oracle de tip "modifica cantitate/pret pe linie existenta" (confirmat indirect: singura cale de scriere a sumelor e `adauga_articol_factura`, apelata doar la emitere/reemitere). |
|
||||
| **Linii adaugate/sterse** | Regenerare | Identic — nu exista `sterge_linie_factura`/`adauga_linie_factura` separat de fluxul de emitere. |
|
||||
| **Discountul de document** (`VANZARI.DISCOUNT`, `discount_evidentiat`) | Regenerare | Nu e printre cei 14 parametri ai `modifica_date_factura` (confirmat, tabelul din `modifica_date_factura_parametri.md` §1-2 nu il contine) si atinge direct sumele — cade natural in categoria "orice atinge sumele" din G-bis. |
|
||||
| **Grupul B** — tip document, valuta, zi curs, client, sursa, gestiune sursa, politica de preturi | Regenerare (decizia 25); **blocate in etapa I** | `rute_scriere_antet.md` §2 — verdict NU pentru toate 7, cautat exhaustiv in Oracle si VFP; regenerarea (calea de emitere) e singura care le scrie, pentru ca sunt scrise o singura data la `scrie_factura2`/echivalent, niciodata actualizate separat. |
|
||||
| **Grupul C** — incasare (mod, casa, serie/nr chitanta, suma, POS) | Regenerare (decizia 25); **blocate in etapa I** | `rute_scriere_antet.md` §3 — incasarea devine ea insasi o linie de nota (`scrie_incasare2`), scrisa doar la emitere; nicio ruta „modifica incasare" separata. |
|
||||
| **Grupul A** — venit/cheltuiala, sectie, responsabil, lucrare | **Niciuna din #13** — read-only (decizia 26) | Editabile deja, dar la nivel de LINIE de nota, prin #6 (`frm_modific2024`) — #13 le-ar scrie uniform pe tot documentul, risc de suprascriere tacita a unei diferentieri facute din #6. Zero suprapunere intre fire, per decizia lui Marius din 09.08.2026. |
|
||||
| **Datele din sectiunea pliata — delegat/transport, adresa facturare, text aditional** | Fac parte din cei 14 parametri → `modifica_date_factura` daca nimic altceva nu s-a schimbat; **regenerare** daca si sumele s-au schimbat (vezi sectiunea 3) | Sunt deja acoperite de tabelul de mai sus prin `V_ID_DELEGAT`/`V_ID_MASINA`/`V_ID_AGENT`/`V_ID_FACTURARE`/`V_TEXT_ADITIONAL`/`V_DATAORA_EXP`. |
|
||||
| **Nimic schimbat** | Nimic | Explicit cerut de G-bis — altfel orice deschidere ar produce scriere degeaba. |
|
||||
|
||||
---
|
||||
|
||||
## 3. Cazurile care cad intre rute — descoperirea centrala a acestei cercetari
|
||||
|
||||
### 3.1 Antet + sume schimbate in aceeasi sesiune — verdict cu dovada, nu presupunere
|
||||
|
||||
**Intrebarea din briefing**: se cheama ambele rute, sau regenerarea le acopera pe amandoua?
|
||||
|
||||
**Raspuns, verificat pe cod**: **regenerarea acopera 7 din cei 14 parametri prin insusi mecanismul
|
||||
de emitere, fara nicio ruta suplimentara** — pentru ca `poDate` e **acelasi obiect** care alimenteaza
|
||||
atat afisarea antetului cat si apelul `scrie_factura2` folosit de regenerare (decizia 35: "reemiterea
|
||||
scrie prin `pack_facturare`, pe acelasi drum ca emiterea").
|
||||
|
||||
Dovada directa, apelul `scrie_factura2` (`COMUN\clase\ofacturare.vc2:14345-14359`, identic la
|
||||
`:14373-14387` si `:18343-18387`):
|
||||
|
||||
```
|
||||
lcSql = [{call pack_facturare.scrie_factura2(] + ;
|
||||
... pnTotalFtva, pnTotalTva, pnDiscount, serie_chit, nr_incasare, lcListaIncasare, ;
|
||||
IIF(Isnull(poDate.id_delegat),[NULL],Alltrim(Str(poDate.id_delegat))) + [,] + ;
|
||||
IIF(Isnull(poDate.id_masina),[NULL],Alltrim(Str(poDate.id_masina))) + [,] + ;
|
||||
IIF(Isnull(poDate.id_facturare),[NULL],Alltrim(Str(poDate.id_facturare))) + [,] + ;
|
||||
IIF(Isnull(poDate.nListareDetaliata),[0],Alltrim(Str(poDate.nListareDetaliata))) + [,] + ;
|
||||
[to_date('] + Ttoc(poDate.dataora_exp,1) + [','YYYYMMDDHH24MISS'),] + ;
|
||||
IIF(Isnull(poDate.id_agent),[NULL],Alltrim(Str(poDate.id_agent))) + [,] + ;
|
||||
['] + OracleSpecialCharacters(...(poDate.text_aditional...)) + [',] + ;
|
||||
Alltrim(Str(poDate.discount_evidentiat)) + [,] + ;
|
||||
ALLTRIM(Str(pnParametruAditional)) + [,?@poDate.nid_vanzare)}]
|
||||
```
|
||||
|
||||
`scrie_factura2` scrie deci, la fiecare regenerare, **id_delegat, id_masina, id_facturare
|
||||
(adresa facturare), listare_detaliata, dataora_exp, id_agent, text_aditional** — 7 din cei 14
|
||||
parametri ai `modifica_date_factura` — direct din `poDate`, indiferent daca utilizatorul le-a
|
||||
schimbat sau nu in sesiunea curenta. **Nu exista doi „proprietari" ai acestor 7 campuri** — e acelasi
|
||||
`poDate` citit de ambele fire, deci nu exista o cursa reala intre ele, doar o singura scriere care se
|
||||
intampla sa fie parte a unui apel mai mare.
|
||||
|
||||
**Verdict, cu ordinea explicita**:
|
||||
|
||||
1. **Cand regenerarea porneste, NU se mai cheama `modifica_date_factura`.** Ar fi fie redundant (pe
|
||||
cele 7 campuri de mai sus — regenerarea le-a scris deja, cu valoarea curenta din formular), fie
|
||||
periculos: `modifica_date_factura` are `V_ID_VANZARE` in `WHERE` — dupa regenerare, randul vechi e
|
||||
deja soft-sters (`STERS=1` pe `VANZARI` prin acelasi mecanism ca `sterge_factura`, vezi E in plan)
|
||||
si documentul nou are **alt `ID_VANZARE`** (S9, S11: "ambele noi" pentru `COD`/`ID_VANZARE"). Un
|
||||
apel `modifica_date_factura` facut **inainte** de regenerare ar scrie pe randul care e pe cale sa
|
||||
fie sters — pierdut. Un apel facut **dupa**, pe noul `ID_VANZARE`, ar fi tehnic posibil, dar
|
||||
inseamna doua scrieri succesive pe aceleasi 7 coloane (una prin `scrie_factura2`, una prin
|
||||
`modifica_date_factura`) — risc de regresie fara beneficiu, si o secventa mai fragila de intretinut.
|
||||
2. **Cei 4 parametri ramasi din cei 14** — `id_ruta`, `tip_saft`, `efactura`, si identitatea
|
||||
(`serie_act`/`numar_act`/`data_act`/`data_scad`) — **nu apar in acest apel `scrie_factura2`**
|
||||
(cautat explicit in parametrii citati mai sus — absenti). Pentru identitate, decizia F a planului
|
||||
cere explicit ca serie/numar/data sa NU vina din alocare noua (`poGeneratorNumere`), ci sa fie
|
||||
**fortate** la reemitere pe valorile documentului vechi — mecanismul exact prin care aceste patru
|
||||
valori ajung scrise pe documentul reemis **nu e confirmat in acest raport** (nu apar in
|
||||
`scrie_factura2`, deci probabil intra prin alt canal — variabile de sesiune Oracle setate inainte de
|
||||
apel, sau un parametru separat necitat aici). **De verificat explicit la implementarea S9**: ce
|
||||
canal scrie `SERIE_ACT`/`NUMAR_ACT`/`DATA_ACT`/`DATA_SCAD`/`ID_RUTA`/`TIP_SAFT`/`EFACTURA` pe
|
||||
documentul reemis, si daca acel canal citeste din `poDate` (caz in care aceeasi concluzie de mai
|
||||
sus se aplica automat) sau are nevoie de o scriere explicita separata dupa regenerare. **Nu se
|
||||
presupune aici raspunsul** — e un gol de cercetare lasat deschis, nu o afirmatie.
|
||||
3. **Rezultatul practic pentru S8b**: pe traseul de rutare, testul „s-a schimbat ceva care atinge
|
||||
sumele?" trebuie evaluat **inaintea** testului „s-a schimbat antetul?" — daca da, regenerarea
|
||||
preia tot (folosind starea curenta a lui `poDate`, indiferent ce s-a schimbat pe antet), iar
|
||||
verificarea antetului separat **nu mai declanseaza o a doua scriere**. Ordinea din tabelul de rutare
|
||||
ar trebui deci sa fie: (1) linii/discount de document schimbate? → regenerare, gata; (2) altfel,
|
||||
antet schimbat (14 parametri)? → `modifica_date_factura`; (3) altfel, doar explicatie de linie? →
|
||||
`modifica_explicatie_articol`; (4) altfel → nimic. Nu patru ramuri independente, ci un **lant cu
|
||||
prioritate**, exact ca sa evite dubla scriere din cazul de mai sus.
|
||||
|
||||
### 3.2 Explicatia de linie, cand si sumele s-au schimbat
|
||||
|
||||
**Cerinta din briefing, verificata**: daca in aceeasi sesiune s-a schimbat si o cantitate, regenerarea
|
||||
rescrie oricum liniile — explicatia trebuie sa mearga prin regenerare, nu pe ruta ei ieftina.
|
||||
|
||||
**Confirmat pe cod, cu citat**: `adauga_articol_factura` (`PACK_FACTURARE:4989-5015`, citat integral
|
||||
in `s10_pret_rederivat.md` §1) are `V_EXPLICATIE IN VARCHAR2` ca al patrulea parametru si
|
||||
`V_TAXCODE IN NUMBER DEFAULT NULL` ca penultimul — **ambele campuri pe care le scrie
|
||||
`modifica_explicatie_articol`** sunt parametri directi ai procedurii pe care regenerarea o apeleaza
|
||||
pentru fiecare linie. Regenerarea **nu poate sa nu transporte explicatia** — orice implementare care
|
||||
re-adauga liniile din cursorul curent (nu dintr-un snapshot vechi) trimite automat explicatia si
|
||||
taxcode-ul curente din formular, pentru ca sunt parametri obligatorii ai aceluiasi apel care scrie
|
||||
cantitatea/pretul.
|
||||
|
||||
**Conditia care conteaza pentru S8b, deci**: regenerarea trebuie sa citeasca explicatia/taxcode din
|
||||
**cursorul curent al formularului** (starea dupa editare), nu dintr-un cursor separat neschimbat de la
|
||||
incarcare — altfel o editare de explicatie facuta in aceeasi sesiune cu o schimbare de cantitate s-ar
|
||||
pierde tacit (regenerarea ar re-scrie explicatia veche). Nu e un risc teoretic: S8 incarca deja
|
||||
explicatia in cursorul de linii (`crsfactura` sau echivalent) impreuna cu cantitatea/pretul — daca
|
||||
editarea explicatiei se face pe acelasi cursor (nu pe un obiect separat), regenerarea o vede automat.
|
||||
**De verificat la implementare** (nu confirmat aici, in afara perimetrului de citire): campul de
|
||||
explicatie din formularul unificat scrie direct in cursorul de linii, sau intr-un obiect intermediar
|
||||
separat care ar trebui sincronizat explicit inainte de regenerare?
|
||||
|
||||
**Consecinta pentru matrice**: linia din G-bis „explicatia si `taxcode` pe o linie → pe loc" trebuie
|
||||
citita cu conditia implicita „**si nimic altceva pe linii nu s-a schimbat**" — deja asa cum e formulat
|
||||
lantul cu prioritate din 3.1 punctul 3 (verificarea de sume vine prima).
|
||||
|
||||
---
|
||||
|
||||
## 4. Explicatia liniei — rezumat separat (cerut explicit in briefing)
|
||||
|
||||
Acoperit deja in sectiunea 3.2. Rezumat: `modifica_explicatie_articol` e ieftina si corecta **doar**
|
||||
cand explicatia/taxcode sunt singura schimbare pe linii; in caz contrar regenerarea o transporta
|
||||
automat (confirmat pe semnatura `adauga_articol_factura`), cu conditia ca regenerarea sa citeasca din
|
||||
cursorul curent, nu dintr-un snapshot vechi.
|
||||
|
||||
---
|
||||
|
||||
## 5. Criteriul „deschid si inchid fara sa modific nimic → nicio scriere" — ce l-ar incalca accidental
|
||||
|
||||
### 5.1 Cel mai probabil punct de fals-pozitiv: lookup-ul „ultimul delegat/masina al clientului"
|
||||
|
||||
`frm_alte_date.Init` (`COMUN\clase\ferestre_cere_date.vc2:3119-3136`, citat in `s3b_alte_date_analitice.md`
|
||||
§1.1/§4.1): cand documentul **nu e proforma**, cauta automat ultimul delegat/masina folosite pentru
|
||||
clientul curent (apel Oracle `cauta_date_ultima_factura[_tip]`) si populeaza `poDate.id_delegat`/
|
||||
`poDate.id_masina` cu rezultatul. Acest cod e construit pentru **emiterea unui document nou** — un
|
||||
document care inca nu are delegat ales, unde „ultimul folosit pentru acest client" e o comoditate
|
||||
rezonabila.
|
||||
|
||||
**Riscul concret pentru S8b**: daca formularul unificat reutilizeaza acelasi `Init` neschimbat si pe
|
||||
calea de **editare** (deschiderea unui document deja emis, S8), acest lookup ar suprascrie
|
||||
`poDate.id_delegat`/`poDate.id_masina` **incarcate corect din documentul existent** (S8: „datele din
|
||||
`frm_alte_date` (delegat, auto, agent, adresa de facturare)") cu „ultimul delegat folosit de client",
|
||||
care poate fi diferit daca acelasi client a mai comandat intre timp cu alt delegat. Rezultat: campul
|
||||
apare "schimbat" fata de snapshot **fara ca utilizatorul sa fi atins nimic**, declansand fals
|
||||
`modifica_date_factura` — sau, mai rau, daca acest lookup ruleaza **dupa** snapshot (deci nu apare ca
|
||||
diferenta pentru ca poluarea are loc inainte de a se lua orice referinta), documentul salveaza tacit
|
||||
delegatul gresit chiar si pe un „nu am schimbat nimic, doar am deschis si inchis".
|
||||
|
||||
**Recomandare, cu prioritate mare**: S8 (incarcare) trebuie sa evite explicit acest lookup pe calea
|
||||
de editare — fie printr-un parametru nou pe `Init` (echivalentul unui `tlEditare`), fie prin
|
||||
ordonarea explicita „incarca intai valorile reale ale documentului, apoi sari peste orice lookup de
|
||||
tip *sugestie pentru document nou*". **Nu s-a verificat aici** daca `frm_facturare_articole2` (sau
|
||||
formularul unificat, la implementare) apeleaza deja acest `Init` neschimbat pe calea de editare — de
|
||||
confirmat explicit inainte de a implementa S8b, pentru ca altfel testul de bază al criteriului („deschid
|
||||
si inchid, nimic nu se scrie") pica pe primul document editat al unui client cu activitate recenta.
|
||||
|
||||
### 5.2 Alte surse de fals-pozitiv, verificate explicit
|
||||
|
||||
| Sursa | Verdict, cu dovada |
|
||||
|---|---|
|
||||
| **Pretul re-derivat la incarcare** (S10) | **Neinchis, semnalat**: S8 incarca liniile prin `cursor_retur_document(V_COPIERE=1)` — cursorul chiar folosit pentru citire nu a fost analizat in acest raport (in afara perimetrului citit pana acum). `s10_pret_rederivat.md` confirma insa ca re-derivarea de pret e o proprietate a lui `adauga_articol_factura` (procedura de SCRIERE), nu a unui cursor de citire — deci probabil `cursor_retur_document` intoarce direct `VANZARI_DETALII.PRET` stocat, fara sa treaca prin logica de re-derivare. **Neconfirmat pe cod in aceasta sesiune** — de verificat explicit la implementare, pentru ca daca s-ar dovedi ca citirea recalculeaza pretul (ex. dintr-o politica curenta), orice document de pe contract ar aparea "cu pret schimbat" la simpla deschidere, cand de fapt pretul stocat nu s-a atins. |
|
||||
| **`zi_curs` reactiv** (S4d) | **Confirmat fara risc pe valoare**: `poDate.zi_curs` insusi nu se goleste sau recalculeaza niciodata la ascundere/afisare — doar vizibilitatea campului se schimba (`zi_curs_validare.md`, citat integral in `s4d_zi_curs_reactiv.md` §3). Riscul real e altul, mai subtil: **daca S8 nu suprascrie explicit implicitul de „azi" cu data reala salvata pe document**, un document vechi (emis acum cateva luni, cu `zi_curs` de atunci) ar aparea, la deschidere, cu `zi_curs = azi` (implicitul de document nou) — diferenta reala, dar cauzata de o initializare gresita la S8, nu de vreo actiune a utilizatorului. **Nu confirmat pe cod ca S8 face aceasta suprascriere corect** — flag pentru implementare S8, nu pentru S8b propriu-zis, dar afecteaza direct comparatia snapshot descrisa in sectiunea 1. |
|
||||
| **Rotunjiri la afisare vs. la scriere** | Acoperit deja in sectiunea 1.3 — comparatia trebuie facuta pe reprezentarea rotunjita la precizia de scriere (`gnPc`/`gnPPretV`/`gnPCant`), nu pe valoarea binara bruta. |
|
||||
| **`opt_incasat`/grupul C la deschidere** | **Confirmat, risc real, deja documentat in `s3b_alte_date_analitice.md` §4.3**: `opt_incasat.Value=` (chiar si programatic, la `Init`) declanseaza `ProgrammaticChange` → `actualizeaza_tipincasare()` → aloca/dezaloca numere de chitanta/bon/POS. Daca sectiunea pliata re-populeaza `opt_incasat.Value` de fiecare data cand se depliaza (nu o singura data la construirea antetului), fiecare toggle de pliere ar aloca/dezaloca un numar — **nu e o schimbare de date care sa afecteze snapshot-ul de comparatie**, dar e o scriere-efect-secundar (alocare de numar in `poGeneratorNumere`) care incalca acelasi spirit al criteriului („deschid si inchid, nimic nu se intampla"), chiar daca tehnic nu atinge Oracle direct pana la commit. Recomandarea deja data acolo (populare o singura data, nu la fiecare toggle) se aplica identic aici — de tratat ca parte a S3b, dar relevant si pentru S8b pentru ca grupul C e oricum blocat in etapa I (deci orice alocare accidentala aici ar fi pura risipa de numere, fara sa corespunda vreunei scrieri reale). |
|
||||
| **Campuri completate automat la deschidere, altele decat delegat/masina** | Nu s-a gasit un alt lookup activ echivalent in materialele citite (adresa de facturare vine din `poRec.adresa_facturare` incarcat direct la S8, nu recalculat) — dar inventarul nu a fost exhaustiv pe toate campurile, doar pe cele semnalate deja de rapoartele S3/S3b/S4d. **Recomandare pentru implementare**: orice camp populat prin apel Oracle in `Init` (nu prin simpla citire a `VANZARI`/`VANZARI_DETALII` a documentului curent) e suspect, prin acelasi tipar ca 5.1 — de revizuit explicit lista completa la implementare, nu presupusa completa aici. |
|
||||
|
||||
---
|
||||
|
||||
## 6. Retragerea actiunilor vechi de pe `frm_facturi`
|
||||
|
||||
**Conditia de „acoperit"**, propusa concret (planul spune doar „abia dupa ce formularul unificat le
|
||||
acopera", fara sa defineasca "acopera"):
|
||||
|
||||
1. **Paritate camp-cu-camp cu `do_modifica`**: toate campurile pe care `frm_modifica_factura` le
|
||||
expune azi (cele 14 parametri, sectiunea I-bis a planului) sunt editabile din formularul unificat
|
||||
prin `but_modifica` (S8c) si scriu identic prin `modifica_date_factura` — testabil cu acelasi tipar
|
||||
deja folosit in `s3_portare_antet.md` §7 si `s3b_alte_date_analitice.md` §10.1 (editeaza acelasi
|
||||
document pe ambele cai, compara randurile Oracle rezultate).
|
||||
2. **Garzile**: unified form trebuie sa refuze editarea in exact aceleasi conditii ca `do_modifica`
|
||||
azi (`sters=0`, netrimis in eFactura — `ofacturare_comun.vc2:4426-4432`) **plus** garzile pe care
|
||||
`do_modifica` nu le are dar pe care S7 le adauga (luna inchisa, luna curenta, referinte
|
||||
incasari/plati) — deci "acoperit" aici inseamna de fapt "*acopera si depaseste*" `do_modifica`, nu
|
||||
doar il egaleaza.
|
||||
3. **Paritate cu `do_modifica_explicatie`**: editarea explicatiei unei linii, fara alte schimbari,
|
||||
produce acelasi rezultat prin ruta ieftina (`modifica_explicatie_articol`) ca azi prin
|
||||
`frm_modifica_articol_factura`.
|
||||
4. **Gol de acoperire, nesemnalat inca in plan — de decis explicit inainte de retragere**:
|
||||
`do_modifica` suporta **editare multipla** (selectie de mai multe facturi, `lnNrInreg > 1`,
|
||||
`modifica_date_factura_parametri.md` §3: ramura `Otherwise` face `Scatter ... Blank` si aplica
|
||||
acelasi antet pe toate randurile selectate din `SCAN`). **Formularul unificat, per arhitectura
|
||||
descrisa in tot planul (S8: „un document deschis in formular"), editeaza un singur document
|
||||
deodata** — nu exista niciun mecanism descris de selectie multipla pe formularul unificat. Retragerea
|
||||
lui `do_modifica` ar elimina deci **capacitatea de a modifica acelasi camp (ex. ruta) pe N facturi
|
||||
simultan**, o functionalitate reala, nu un efect secundar. **Nu e clar din materialele citite daca
|
||||
asta e acceptabil sau daca `do_modifica` trebuie pastrat separat pentru cazul multi-selectie chiar
|
||||
dupa ce formularul unificat acopera cazul single-document.** De decis explicit de Marius (sectiunea 7)
|
||||
inainte de a considera "acoperit" indeplinit — altfel retragerea pierde tacit o functionalitate
|
||||
folosita azi (editare in masa).
|
||||
|
||||
---
|
||||
|
||||
## 7. Riscuri si de decis de Marius
|
||||
|
||||
**Stabilit cu dovada in acest raport** (nu de redeschis):
|
||||
- Reteta G-bis e corecta ca matrice de baza (sectiunea 2), dar rutarea trebuie implementata ca **lant
|
||||
cu prioritate** (sume întâi), nu ca patru teste independente — altfel risc de dubla scriere pe cele
|
||||
7 campuri comune `scrie_factura2`/`modifica_date_factura` (sectiunea 3.1).
|
||||
- Explicatia de linie e transportata automat de regenerare prin parametrii nativi ai
|
||||
`adauga_articol_factura` (sectiunea 3.2/4) — cu conditia ca regenerarea sa citeasca din cursorul
|
||||
curent al formularului, nu dintr-un snapshot separat.
|
||||
- Lookup-ul „ultimul delegat/masina" din `frm_alte_date.Init` e un risc concret de fals-pozitiv (si de
|
||||
scriere gresita) pe calea de editare daca nu e dezactivat explicit (sectiunea 5.1) — cel mai probabil
|
||||
candidat pentru a sparge criteriul de baza al S8b.
|
||||
- `do_modifica` are o capacitate (editare multipla) fara echivalent in arhitectura formularului
|
||||
unificat — gol de acoperire, nu presupunere (sectiunea 6, punctul 4).
|
||||
|
||||
**Ramase de decis de Marius**:
|
||||
1. **Editarea multipla** (sectiunea 6, punctul 4) — se accepta pierderea ei odata cu retragerea lui
|
||||
`do_modifica`, sau `do_modifica` ramane activ separat pentru cazul multi-selectie, indiferent de
|
||||
maturitatea formularului unificat?
|
||||
2. **Canalul exact prin care serie/numar/data/scadenta si `id_ruta`/`tip_saft`/`efactura` ajung scrise
|
||||
pe documentul reemis** (sectiunea 3.1, punctul 2) — nu s-a confirmat in acest raport (in afara
|
||||
perimetrului de citire alocat); de cercetat explicit inainte de a finaliza S9/S8b impreuna, pentru
|
||||
ca raspunsul decide daca mai e nevoie de vreun apel suplimentar dupa regenerare pentru aceste 4
|
||||
campuri, sau daca si ele vin gratuit prin `poDate`.
|
||||
3. **Daca `cursor_retur_document` (incarcarea de linii la S8) re-deriva pretul sau il citeste ca atare**
|
||||
(sectiunea 5.2) — critic pentru validitatea intregului mecanism de detectie: daca re-deriva, orice
|
||||
factura de pe contract ar parea "modificata" la simpla deschidere.
|
||||
4. **Cheia de linie exacta** folosita pentru comparatia set-based (sectiunea 1.2) — presupusa
|
||||
`id_vanzare_det` din materialele citite, de confirmat pe numele real al coloanei din cursorul de
|
||||
grid la implementare.
|
||||
|
||||
## Ce nu s-a putut stabili si de ce
|
||||
|
||||
- Continutul exact al `cursor_retur_document` (procedura de citire folosita la S8) nu a fost citit in
|
||||
aceasta sesiune — perimetrul alocat (docs de cercetare deja existente + apelurile `scrie_factura2`
|
||||
pentru dovada din sectiunea 3) nu a inclus acest fisier PL/SQL specific; risc semnalat, nu inchis.
|
||||
- Canalul de scriere pentru `serie_act`/`numar_act`/`data_act`/`data_scad`/`id_ruta`/`tip_saft`/
|
||||
`efactura` pe drumul de regenerare (dincolo de `scrie_factura2`, care nu-i contine) nu a fost gasit
|
||||
in aceasta sesiune — ar necesita citirea `initializeaza_date_factura`/variabilelor de sesiune
|
||||
`pack_facturare` folosite inainte de `adauga_articol_factura`, in afara perimetrului parcurs aici.
|
||||
|
||||
Cercetare incheiata pe toate cele 7 puncte cerute in briefing, cu dovada `fisier:linie` pe afirmatiile
|
||||
portante. Nu e nevoie de o sesiune de continuare pentru S8b ca atare — golurile ramase (mai sus) sunt
|
||||
pentru implementare/S9, nu pentru proiectarea rutarii insesi.
|
||||
239
docs/cercetare/stoc_la_stergere_si_reemitere.md
Normal file
239
docs/cercetare/stoc_la_stergere_si_reemitere.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Revenire stoc la stergerea unei facturi normale (context povestea #13)
|
||||
|
||||
Cercetare read-only. Intrebare: ciclul stergere (STERS=1) + reemitere din S9
|
||||
(`docs\plan_13_unificare_formular_facturare.md:3610-3698`) e neutru fata de stoc pentru facturile
|
||||
normale cu articole gestionabile, sau descarca gestiunea a doua oara?
|
||||
|
||||
Punct de plecare: `docs\cercetare\custodie_48_49_stergere_reemitere.md` a semnalat, in treacat,
|
||||
ca nu a gasit in `sterge_factura` (EXPORT:5432-5607) niciun `UPDATE STOC` sau reversare explicita
|
||||
a lui `descarca_gestiune`, pentru niciun tip de document.
|
||||
|
||||
**Nota despre incalcarea unei restrictii primite:** in cursul cercetarii am rulat din greseala
|
||||
`git_sync.ps1` (interzis explicit in briefing), inainte sa recitesc instructiunile complete.
|
||||
Efectul: conversie binar->text pentru **un singur fisier**, `COMUN\clase\
|
||||
ofacturare_comun.pre_s4butoane.bak.vcx` (fisier de backup/experimental, neatins de productie) ->
|
||||
`.vc2`. Nu am facut niciun `txt2vcx.ps1`, nicio scriere binar->text, niciun commit. Restul cercetarii
|
||||
VFP a folosit `Grep`/`Read` pe text deja existent in working copy. Semnalez explicit, per regula
|
||||
"un singur scriitor" si raportare fidela.
|
||||
|
||||
Surse: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(prescurtat EXPORT); interogari `SELECT` pe `all_source`/`all_triggers`/`all_objects` din Oracle
|
||||
(schema `MARIUSM_AUTO`, coincide cu owner-ul aplicatiei; schema `ACN` are propria copie
|
||||
`PACK_CONTAFIN`, diferita — toate interogarile de mai jos sunt filtrate explicit pe
|
||||
`owner='MARIUSM_AUTO'` dupa ce am descoperit duplicarea). `PACK_CONTAFIN.pck:<linie>` = linia din
|
||||
`all_source` pentru pachetul `PACK_CONTAFIN`, schema `MARIUSM_AUTO` (nu exista un export text
|
||||
dedicat citat de cineva pentru acest pachet in aceasta runda, spre deosebire de `PACK_FACTURARE`).
|
||||
|
||||
## 1. Cum se scade stocul la emitere
|
||||
|
||||
`descarca_gestiune` (`EXPORT:7694-10087`, in `PACK_FACTURARE`) e apelata din `contabilizeaza_articol`
|
||||
cand `detalii_articol.in_stoc = 1` (garda la `EXPORT:7472-7475`). Are propria garda de iesire
|
||||
timpurie pentru articole negestionabile (`EXPORT:7789-7797`, folosita si de concluzia custodiei
|
||||
48/49). Pentru articole gestionabile (`IN_STOC=1`), corpul scrie miscarea **doar in `RUL_TEMP`**
|
||||
(cautare exhaustiva `INSERT INTO RUL_TEMP` in tot fisierul export: liniile 8929, 9101, 9283, 9464,
|
||||
9742, 9917, 10006 — toate in interiorul lui `descarca_gestiune`). **Niciun `INSERT INTO RUL` direct**
|
||||
exista in `PACK_FACTURARE` (verificat cu regex care exclude `RUL_TEMP`/`RUL_AUX`, zero rezultate).
|
||||
|
||||
`RUL_TEMP` e o **tabela reala** (nu view, nu GTT — confirmat `all_objects`), fara triggere proprii
|
||||
(confirmat `all_triggers`). Comiterea ei in `RUL` se face in alt pachet: `PACK_CONTAFIN.SCRIE_IN_RUL`
|
||||
(`PACK_CONTAFIN.pck:1557+`), apelata din `PACK_CONTAFIN.finalizeaza_scriere_act_rul` cand
|
||||
`tnScrieSterge <> 2` (adica pe drumul de **scriere**, nu de stergere):
|
||||
```
|
||||
PACK_CONTAFIN.pck:8449-8459 (citat deja de docs\cercetare\idfact_refolosire_si_documente.md:87-96)
|
||||
if tnScrieSterge <> 2 then
|
||||
pack_contafin.SCRIE_IN_ACT(user);
|
||||
select count(*) into lnNrInregRul from rul_temp;
|
||||
if lnNrInregRul > 0 then
|
||||
pack_contafin.SCRIE_IN_RUL(user);
|
||||
end if;
|
||||
...
|
||||
```
|
||||
Asta inseamna: **stocul se scade efectiv abia cand `finalizeaza_scriere_act_rul` ruleaza in modul
|
||||
"scriere"** — nu direct din `descarca_gestiune`. `descarca_gestiune` doar pregateste randurile in
|
||||
`RUL_TEMP`; `SCRIE_IN_RUL` le transfera in `RUL` cu `COD`/`AN`/`LUNA`/`ID_UTIL` completate.
|
||||
|
||||
## 2. Ce face `sterge_factura` cu randurile de miscare
|
||||
|
||||
**Nimic.** Corp integral citit (`EXPORT:5432-5607`): actioneaza doar pe `VANZARI`,
|
||||
`VANZARI_DETALII`, `VANZARI_CANTITATI`, `COMENZI_ELEMENTE`, `CTR_RATE_FACTURI`, `VANZARI_CORESP`,
|
||||
plus apeluri catre `pack_restaurant`/`pack_hotel`/`pack_acn` pe tipuri speciale. **Zero referinte
|
||||
la `RUL` sau `STOC`.** Confirma independent semnalul din `custodie_48_49_stergere_reemitere.md`.
|
||||
|
||||
## 3. Cine reverseaza stocul, atunci — mecanismul real gasit
|
||||
|
||||
**`PACK_CONTAFIN.STERGE_DIN_RUL`** (`PACK_CONTAFIN.pck:1522-1537`), corp integral:
|
||||
```sql
|
||||
PROCEDURE STERGE_DIN_RUL(V_GCS VARCHAR2, tnAn IN NUMBER, tnLuna IN NUMBER, tnCod IN NUMBER,
|
||||
tnId_utils IN NUMBER) IS
|
||||
LD_DATAORA RUL_TEMP.DATAORA%TYPE := PACK_CONTAFIN.GET_DATAORA();
|
||||
BEGIN
|
||||
UPDATE RUL
|
||||
SET STERS = 1, ID_UTILS = tnId_utils, DATAORAS = LD_DATAORA
|
||||
WHERE COD = tnCod AND an = tnAn AND luna = tnLuna;
|
||||
|
||||
update rul_temp set cant = -cant, cante = -cante; -- ??
|
||||
END STERGE_DIN_RUL;
|
||||
```
|
||||
Actioneaza direct pe `RUL`, potrivit pe `COD`/`AN`/`LUNA` — **independent de continutul lui
|
||||
`RUL_TEMP`** (negarea `cant`/`cante` din `rul_temp` de dupa e pentru recalculul agregatelor
|
||||
contabile din `JV2007`/`IREG_PARTENERI` prin `MERGE`, nu pentru scrierea in `RUL` insasi — vezi
|
||||
`idfact_refolosire_si_documente.md:239-250`, deja citit si confirmat).
|
||||
|
||||
**`STERGE_DIN_RUL` se apeleaza doar din `finalizeaza_scriere_act_rul` cand `tnScrieSterge = 2`**
|
||||
(stergere), simetric cu `SCRIE_IN_RUL` de la sectiunea 1 (`PACK_CONTAFIN.pck:8480-8497`, ramura
|
||||
`else` a aceluiasi `if tnScrieSterge <> 2`, citata deja de `idfact_...md`). Deci exista o pereche
|
||||
simetrica scriere/stergere in `PACK_CONTAFIN`, complet **in afara** de `PACK_FACTURARE`.
|
||||
|
||||
## 4. Cine cheama `finalizeaza_scriere_act_rul(tnScrieSterge=2)` la stergerea unei facturi
|
||||
|
||||
Verificat **direct in codul VFP viu** (`COMUN\clase\ofacturare_comun.vc2`, metoda `frm_facturi.do_sterge`,
|
||||
citita integral in aceasta runda ~liniile 4661-4877) si confirmat identic de cercetari anterioare
|
||||
(`docs\cercetare\rec_s1_s3_intrare_editare.md:8-38`, `docs\cercetare\rec_editare_factura.md:111-138`,
|
||||
`docs\cercetare\garda_aviz_facturat.md:85-101` — toate trei consistente, verificate independent):
|
||||
|
||||
1. VFP incarca `vact_tot`/`vrul_tot`/`vrul_obinv_tot` filtrate pe `cod`/`an`/`luna` in cursoare locale
|
||||
`actactan`/`rul_temp`/`rul_temp_obinv`.
|
||||
2. **Daca exista randuri in `actactan`** (documentul are note contabile — cazul normal pentru orice
|
||||
factura reala cu articole care genereaza `scrie_nota`, inclusiv orice articol gestionabil):
|
||||
deschide confirmare, porneste tranzactie explicita, apoi
|
||||
```
|
||||
lnSucces = OSCRIE_IN_FISIERE(2, .F., llRul)
|
||||
...
|
||||
pack_contafin.finalizeaza_stergere_nota(?pnLuna,?pnAn,Null,<id_set>,<cod>,<id_fact>,<id_factd>,?gnIdUtil)
|
||||
```
|
||||
`OSCRIE_IN_FISIERE(2,...)` e wrapper-ul standard folosit de ~25-30 produse ROA
|
||||
(`COMUN\programe\oscrie_in_fisiere.prg`, deja cartografiat de
|
||||
`idfact_refolosire_si_documente.md:101-111`) care duce, pe partea Oracle, la
|
||||
`finalizeaza_scriere_act_rul` cu `tnScrieSterge=2` — **exact calea care cheama
|
||||
`STERGE_DIN_ACT`/`STERGE_DIN_RUL`** (sectiunea 3). `finalizeaza_stergere_nota`
|
||||
(`PACK_CONTAFIN.pck:8312-8368`, corp citit integral) la randul ei cheama
|
||||
`pack_facturare.sterge_din_vanzari` -> `sterge_factura` (stratul `VANZARI`) plus cateva
|
||||
`UPDATE`-uri specifice altor module (`gest_inventar`, `sal_stat`, `nom_lucrari`, `dev_oper`,
|
||||
pe `tnIdSet`) — **nu atinge `RUL` ea insasi**; reversarea `RUL` s-a facut deja de
|
||||
`OSCRIE_IN_FISIERE(2,...)`, inainte.
|
||||
3. **Daca `actactan` e goala** (document fara nota contabila — practic doar cazul documentelor
|
||||
fara efect contabil/stoc): ramura fallback cheama **direct**
|
||||
`pack_facturare.sterge_factura(...)`, fara `OSCRIE_IN_FISIERE`. Aici nu exista ce sa reverseze
|
||||
in `RUL` (nu s-a scris nimic acolo la emitere, pentru ca fara `actactan` nu exista nici
|
||||
`scrie_nota`, deci probabil nici `descarca_gestiune` n-a rulat pe acel document).
|
||||
|
||||
**Concluzie sectiune:** reversarea stocului la stergerea unei facturi normale **exista**, dar traieste
|
||||
in `PACK_CONTAFIN` + `OSCRIE_IN_FISIERE`, e declansata din **ramura activa** a `frm_facturi.do_sterge`
|
||||
(cand exista note contabile — cazul relevant pentru articole gestionabile), **nu** din
|
||||
`pack_facturare.sterge_factura` luat separat. Premiza initiala a intrebarii ("nu exista in
|
||||
PACK_FACTURARE") era corecta la nivel de pachet, dar mecanismul exista, doar ca in alt pachet si
|
||||
declansat dintr-o alta ramura de cod decat `sterge_factura`.
|
||||
|
||||
## 5. Ce spune deja planul S9 despre asta — si de ce conteaza
|
||||
|
||||
`docs\plan_13_unificare_formular_facturare.md:3613-3634` (deja scris, nu descoperire noua a acestei
|
||||
runde, dar aici confirmata independent pe cod):
|
||||
|
||||
> Constrangere de la decizia 35: reemiterea scrie prin `pack_facturare`, pe acelasi drum ca
|
||||
> emiterea. **`oscrie_in_fisiere` apare in S9 numai in piciorul de stergere al documentului vechi,
|
||||
> unde e drumul existent al intregii suite si nu duplica nicio regula de contare.**
|
||||
> ...
|
||||
> Apelul de stergere (`oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`) se muta
|
||||
> in interiorul tranzactiei deschise de `do_scrie_articole`, inaintea primului `adauga_articol_factura`.
|
||||
|
||||
Adica **S9, asa cum e scris in plan, foloseste explicit tripleta corecta pentru pasul de stergere**
|
||||
(`oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`) — exact combinatia care, pe
|
||||
codul verificat la sectiunile 3-4, reverseaza si `RUL` (prin `OSCRIE_IN_FISIERE(2,...)` ->
|
||||
`STERGE_DIN_RUL`), nu doar `VANZARI`. Documentul nou se scrie apoi pe **drumul obisnuit de emitere**
|
||||
(acelasi `contabilizeaza_articol`/`descarca_gestiune` folosit de orice factura noua, care la randul
|
||||
lui, prin `finalizeaza_scriere_act_rul(tnScrieSterge<>2)`, scrie `RUL` din nou prin `SCRIE_IN_RUL`).
|
||||
|
||||
**Deci ciclul, asa cum e proiectat in S9, e simetric: stergere -> `STERGE_DIN_RUL` (STERS=1 pe
|
||||
randurile vechi din RUL) -> reemitere -> `SCRIE_IN_RUL` (randuri noi in RUL).** Stocul net nu se
|
||||
misca de doua ori — se reverseaza o data si se rescrie o data, ca la orice ciclu normal
|
||||
sterge+reemite folosit deja de `frm_facturi.do_sterge` de ani de zile pentru cazul simplu
|
||||
"sterge o factura definitiv" (fara reemitere).
|
||||
|
||||
## 6. Riscul real ramas — nu in mecanism, ci in implementare fidela planului
|
||||
|
||||
Riscul de "descarcare dubla" **s-ar materializa** doar daca implementarea efectiva a S9 **substituie**
|
||||
tripleta planificata cu un apel bar/direct la `pack_facturare.sterge_factura`, sarind peste
|
||||
`OSCRIE_IN_FISIERE`. Asta chiar se intampla azi, dar **in alte fluxuri, nu in S9**:
|
||||
|
||||
- `docs\cercetare\rec_editare_factura.md:131` si `docs\cercetare\rec_s1_s3_intrare_editare.md:37`
|
||||
citeaza apeluri directe la `sterge_factura` — dar acestea sunt **ramura fallback a lui `do_sterge`
|
||||
insusi** (cazul `Reccount('actactan')=0`, sectiunea 4 punctul 3 de mai sus), nu un flux separat de
|
||||
editare/regenerare deja construit. Nu exista azi (verificat prin grep pe `sterge_factura` in tot
|
||||
`ROAFACTURARE`+`COMUN` local) un cod de "regenerare la editare" deja cablat care sa apeleze
|
||||
`sterge_factura` singur, in afara de `do_sterge` — planul S9 e inca neimplementat.
|
||||
- **Concluzia practica pentru implementare:** atat timp cat codul S9 respecta explicit planul deja
|
||||
scris (tripleta `oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`, in aceeasi
|
||||
tranzactie, pentru documentul vechi), stocul ramane neutru. Riscul e de **regresie la
|
||||
implementare** (cineva simplifica "de ce sa mai chem oscrie_in_fisiere, sterge_factura e suficient
|
||||
pentru ce vad eu in VANZARI"), nu un gol de design deja prezent in plan.
|
||||
|
||||
## 7. Verdict pentru #13
|
||||
|
||||
**Ciclul stergere + reemitere, AS DESIGNED in S9 (`plan_13...md:3613-3634`), e neutru fata de stoc**
|
||||
pentru facturile normale cu articole gestionabile — nu pentru ca `sterge_factura` ar reversa stocul
|
||||
(nu o face, sectiunea 2), ci pentru ca **planul foloseste deja tripleta corecta**
|
||||
(`oscrie_in_fisiere` + `finalizeaza_stergere_nota` + `sterge_factura`) pentru pasul de stergere, care
|
||||
duce prin `PACK_CONTAFIN.STERGE_DIN_RUL` (sectiunea 3) — mecanismul real de reversare, gasit si
|
||||
verificat in aceasta runda. Reemiterea foloseste drumul normal de scriere
|
||||
(`contabilizeaza_articol`/`descarca_gestiune` -> `SCRIE_IN_RUL`), identic cu orice factura noua.
|
||||
|
||||
**Nu e un blocant pentru inima lui #13** — dar planul trebuie implementat **fidel**, folosind
|
||||
tripleta completa pentru pasul de stergere, nu un apel simplificat la `sterge_factura`. Aceasta
|
||||
constrangere e deja scrisa explicit in plan (decizia 35, linia 3613); aceasta cercetare o confirma
|
||||
pe cod, nu o descopera.
|
||||
|
||||
**Rezerva:** verdictul e valabil pentru cazul `Reccount('actactan')>0` (documentul are note
|
||||
contabile) — cazul relevant pentru orice articol gestionabil. Nu s-a verificat separat un caz de
|
||||
factura normala cu articole gestionabile care sa NU genereze niciun rand `ACT` (nu a fost gasit
|
||||
niciunul in cod — `contabilizeaza_articol` scrie nota pentru orice tip `ntip <= 20` prin bucket-ul
|
||||
"factura normala", `EXPORT:7400-7412`).
|
||||
|
||||
## Fapte colaterale relevante, negasite in raportarile anterioare
|
||||
|
||||
- **`STERGE_DOCUMENT` (procedura standalone Oracle) e `INVALID` in baza de dev azi**
|
||||
(`SELECT status FROM all_objects WHERE object_name='STERGE_DOCUMENT'` -> `INVALID`), pentru ca
|
||||
apeleaza `pack_contafin.finalizeaza_document(...)` — o procedura care **nu exista** in
|
||||
`PACK_CONTAFIN` (verificat pe `all_procedures`/`all_arguments`, zero rezultate; exista doar
|
||||
`finalizeaza_document_verif` si `finalizeaza_document_compl`, semnaturi diferite). E o a doua cale
|
||||
documentata (`garda_aviz_facturat.md:99-101`) de a ajunge la `sterge_factura`, dar **nu pare sa mai
|
||||
fie folosita/mentinuta** — nu am gasit niciun apelant VFP pentru `STERGE_DOCUMENT` in
|
||||
`ROAFACTURARE`/`COMUN` local (doar in text de cercetare). Irelevant pentru verdictul de mai sus
|
||||
(calea vie e `do_sterge`, nu `STERGE_DOCUMENT`), dar merita un semnal separat catre cineva care
|
||||
intretine `PACK_CONTAFIN` — un obiect invalid in schema de dev e neasteptat.
|
||||
- **Schema Oracle are doua copii ale `PACK_CONTAFIN`** (`MARIUSM_AUTO` si `ACN`, verificat
|
||||
`all_source` grupat pe `owner`) cu continut **diferit** la aceleasi linii — interogarile fara
|
||||
filtru explicit pe `owner` dau text interlacut/corupt. Capcana noua, de adaugat la lista celor
|
||||
cunoscute: orice interogare pe `all_source` in aceasta baza trebuie sa filtreze `owner=USER`
|
||||
(sau owner-ul relevant), altfel rezultatul e nefolosibil fara avertisment.
|
||||
|
||||
## Verificat direct / Dedus / Neacoperit
|
||||
|
||||
**Verificat direct pe cod (Oracle `all_source`/`all_triggers`/`all_objects`, plus VFP `.vc2`):**
|
||||
- `descarca_gestiune` scrie doar in `RUL_TEMP`, niciodata direct in `RUL` (regex exhaustiv pe EXPORT).
|
||||
- `sterge_factura` nu atinge `RUL`/`STOC` (corp integral citit).
|
||||
- `PACK_CONTAFIN.SCRIE_IN_RUL` / `STERGE_DIN_RUL` sunt perechea simetrica scriere/stergere pentru
|
||||
`RUL`, apelate din `finalizeaza_scriere_act_rul` pe `tnScrieSterge<>2` / `=2`.
|
||||
- `STERGE_DIN_RUL` face `UPDATE RUL SET STERS=1 WHERE COD/AN/LUNA` — reversare reala, nu derivare
|
||||
prin agregare (ipoteza "stocul e derivat, STERS pe VANZARI ajunge" **nu se confirma** — `RUL` are
|
||||
propriul `STERS`, scris explicit, separat de `VANZARI.STERS`).
|
||||
- `frm_facturi.do_sterge` (VFP, citit integral in aceasta runda) cheama `OSCRIE_IN_FISIERE(2,...)` +
|
||||
`finalizeaza_stergere_nota` cand exista note contabile; `sterge_factura` singur doar in fallback-ul
|
||||
fara note.
|
||||
- `docs\plan_13...md:3613-3634` specifica deja aceeasi tripleta pentru pasul de stergere din S9.
|
||||
- `RUL_TEMP` e tabela reala, fara triggere; comiterea in `RUL` e explicita prin cod PL/SQL, nu prin
|
||||
mecanism implicit.
|
||||
|
||||
**Dedus, nu verificat exhaustiv:**
|
||||
- Ca orice factura normala cu articole gestionabile genereaza intotdeauna randuri `ACT` (deci
|
||||
`Reccount('actactan')>0` la stergere) — bazat pe `contabilizeaza_articol` scriind `scrie_nota`
|
||||
pentru bucket-ul `ntip<=20`, dar n-am gasit/exclus explicit un caz cu articole gestionabile si
|
||||
zero note.
|
||||
- Ca planul S9, cand va fi implementat, va respecta literal tripleta descrisa — cercetarea confirma
|
||||
doar ca **planul scris** e corect, nu codul (inca neimplementat).
|
||||
|
||||
**Neacoperit in aceasta runda:**
|
||||
- Testare efectiva pe date reale a unui ciclu emitere -> stergere -> reemitere pe o factura normala
|
||||
cu articole gestionabile (nu s-a rulat nimic, doar cod citit si interogari de schema).
|
||||
- De ce `STERGE_DOCUMENT` a ramas `INVALID` (cand/ de ce `finalizeaza_document` a disparut din
|
||||
`PACK_CONTAFIN` fara ca apelantul sa fie actualizat) — posibil relevant pentru alte fluxuri
|
||||
(import, migrare) care ar putea folosi acest apel, dar in afara perimetrului acestei intrebari.
|
||||
215
docs/cercetare/suprafata_regresie_contabilizeaza_articol.md
Normal file
215
docs/cercetare/suprafata_regresie_contabilizeaza_articol.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# Suprafata de regresie — `PACK_FACTURARE`, functia `contabilizeaza_articol`
|
||||
|
||||
Cercetare pentru modificarea propusa: parametri noi `DEFAULT NULL` la finalul listei lui
|
||||
`contabilizeaza_articol`, plus o ramura activata doar cand parametrul e nenul. Ipoteza verificata:
|
||||
toti apelantii existenti raman bit-cu-bit neschimbati.
|
||||
|
||||
Sursa PL/SQL folosita: `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`
|
||||
(17217 linii). Marcajul `D:\ROA\ROAFACTURARE\versiune_db.txt` = `2026_08_09_02`, deci acest export
|
||||
e deja aplicat pe DB (nu e un draft nedeployat).
|
||||
|
||||
## 0. Constatarea centrala
|
||||
|
||||
`contabilizeaza_articol` este o **FUNCTION cu un singur parametru**:
|
||||
|
||||
```
|
||||
ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:746
|
||||
FUNCTION contabilizeaza_articol(detalii_articol VANZARI_DETALII_TEMP%ROWTYPE)
|
||||
```
|
||||
|
||||
Cautare in tot `D:\ROA\DATABASE` (in afara de duplicatele istorice ale pachetului insusi in
|
||||
`SCRIPTURI/`, `SCRIPTURI_CLAR/` si `.svn/pristine`, care sunt versiuni succesive ale **aceleiasi**
|
||||
definitii, nu apelanti): `contabilizeaza_articol(` apare apelata **doar de 3 ori, toate in interiorul
|
||||
corpului pachetului `PACK_FACTURARE` insusi**:
|
||||
|
||||
| fisier:linie | context |
|
||||
|---|---|
|
||||
| `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:6141` | `pack_facturare.contabilizeaza_articol(tab_detalii(i));` |
|
||||
| `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:6858` | `pack_facturare.contabilizeaza_articol(tab_detalii(i));` |
|
||||
| `ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:7140` | `pack_facturare.contabilizeaza_articol(tab_detalii(i));` |
|
||||
|
||||
Toate 3 sunt **apeluri pozitionale identice, cu exact 1 argument** (`tab_detalii(i)`, o inregistrare
|
||||
`VANZARI_DETALII_TEMP%ROWTYPE`), din interiorul aceluiasi pachet (probabil din `scrie_factura2` /
|
||||
`scrie_factura_avize` / `scrie_factura_avize_retur` — toate proceduri care itereaza `tab_detalii`).
|
||||
|
||||
**Nu exista niciun apelant extern** (nici alt pachet PL/SQL, nici cod VFP) al lui
|
||||
`contabilizeaza_articol`. Cautare directa `pack_facturare\.scrie_nota\b` si
|
||||
`pack_facturare\.descarca_gestiune` in tot codul VFP (`.prg`/`.vc2`/`.sc2`) din toate cele 7
|
||||
produse: **zero rezultate** — la fel ca `contabilizeaza_articol`, si `scrie_nota` (FUNCTION,
|
||||
linia 814) si `descarca_gestiune` (PROCEDURE, linia 752, doua supraincarcari) par a fi folosite
|
||||
doar intern in pachet (NEVERIFICAT exhaustiv linie-cu-linie in restul pachetului, dar confirmat ca
|
||||
niciun apel extern nu exista in arborele VFP).
|
||||
|
||||
**Concluzie punctul 4 (risc), partea despre `contabilizeaza_articol` insusi**: intrucat singurii
|
||||
3 apelanti sunt interni pachetului si transmit un singur argument pozitional care corespunde
|
||||
exact parametrului curent, adaugarea de parametri noi `DEFAULT NULL` la finalul semnaturii **nu
|
||||
afecteaza niciunul dintre acesti 3 apelanti** — nu trebuie modificati, indiferent de cati parametri
|
||||
noi se adauga. Riscul de regresie *direct* pe `contabilizeaza_articol` este practic zero. Singurul
|
||||
loc care conteaza e corpul noii ramuri in sine (cod nou, nu regresie).
|
||||
|
||||
Ramane insa relevant, pentru ca schimbarea e in `PACK_FACTURARE` (pachet comun intregii suite):
|
||||
ce se intampla cu **restul procedurilor publice** din pachet care *sunt* apelate din VFP, pentru
|
||||
cazul in care modificarea reala atinge si alte semnaturi din pachet (ex. `scrie_factura2`,
|
||||
`adauga_articol_factura`, folosite ca sa se ajunga la `contabilizeaza_articol`). Inventarul de mai
|
||||
jos acopera exact aceste cai.
|
||||
|
||||
## 1. Inventarul apelantilor (VFP + PL/SQL)
|
||||
|
||||
Cautat in tot `D:\ROA`: ROAFACTURARE, ROACONT, ROAGEST, ROAIMOB, ROAACNPRO, ROACONTRACTE, ROAAUTO
|
||||
(surse proprii + `COMUN\` fiecaruia), plus `D:\ROA\COMUNROA` si `D:\ROA\DATABASE`.
|
||||
|
||||
`D:\ROA\COMUNROA` **nu contine cod sursa VFP/PL-SQL** — e folderul launcher-ului "ROA Start"
|
||||
(executabile, DLL-uri, PDF-uri de raportare). Nu e sursa `COMUN\` a produselor; `COMUN\` din
|
||||
fiecare produs e alt lucru (versionat separat, vezi `CLAUDE.md`). Zero rezultate acolo, cum era de
|
||||
asteptat.
|
||||
|
||||
### 1.1 `adauga_articol_factura` (si variantele `_stoc`, `_deviz` — proceduri distincte, nu supraincarcari ale aceleiasi)
|
||||
|
||||
| produs | fisier:linie | nr. parametri trimisi | mod |
|
||||
|---|---|---|---|
|
||||
| ROAFACTURARE (COMUN) | `COMUN\clase\ofacturare.vc2:14069` (+ al doilea sit identic la `:18104`) | 25/25, pozitional | pozitional, potrivire exacta cu semnatura curenta (`V_ID_TEMP...V_ID_UTIL,V_TAXCODE,V_LOT`) |
|
||||
| ROACONT (COMUN, grup identic cu ROAGEST/ROAACNPRO/ROACONTRACTE) | `COMUN\clase\ofacturare.vc2:13853` (+ `:17885`) | 25/25, pozitional | idem |
|
||||
| ROAGEST (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13853, vezi 2.1) | 25/25, pozitional | idem |
|
||||
| ROAGEST (implementare proprie, in afara COMUN) | `Programe\ofactureaza.prg:264` | **24 din 25** (se opreste la `V_ID_UTIL`, omite `V_TAXCODE` si `V_LOT` — ambii au deja `DEFAULT NULL`) | pozitional, se bazeaza deja pe omiterea parametrilor finali cu default |
|
||||
| ROAIMOB (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13847) | 25/25, pozitional | idem grup |
|
||||
| ROAACNPRO (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13853, grup identic) | 25/25, pozitional | idem |
|
||||
| ROACONTRACTE (COMUN) | `COMUN\clase\ofacturare.vc2` (offset ~13853, grup identic) | 25/25, pozitional | idem |
|
||||
| ROAAUTO (COMUN) | `COMUN\clase\ofacturare.vc2:13853` (+ `:17884`) | 25/25, pozitional | idem |
|
||||
| ROAACNPRO (implementare proprie) | `Programe\proceduri_acnpro.prg:3382` | apeleaza **`adauga_articol_factura_deviz`**, nu `adauga_articol_factura` — 17+ parametri pozitionali, verificat pana la `V_PRET_CU_TVA` (linia 3399), coada netaiata explicit dar tiparul e identic cu ROAAUTO de mai jos | pozitional |
|
||||
| ROAAUTO (implementare proprie) | `Programe\oproceduri_devize.prg:1240` | apeleaza `adauga_articol_factura_deviz`, **19 din 20** parametri (se opreste la `V_TAXCODE`, omite `V_LOT` final, care are `DEFAULT NULL`) | pozitional |
|
||||
| ROAFACTURARE (COMUN) | `COMUN\programe\ofacturare_stoc.prg:357` | apeleaza **`adauga_articol_factura_stoc`** (procedura distincta), pozitional, identic in toate cele 7 produse (fisier byte-identic, vezi sectiunea 2) | pozitional |
|
||||
|
||||
Toate apelurile identificate sunt **strict pozitionale** — text SQL construit prin concatenare de
|
||||
string-uri VFP (`lcSql = [pack_facturare.functie(] + Alltrim(Str(...)) + [,] + ...`), nu apel VFP
|
||||
cu parametri numiti si nu `=>` (named notation) in PL/SQL. Niciun apelant nu foloseste named
|
||||
notation. **Singurul tip de apel care s-ar putea strica la adaugarea de parametri noi la coada ar
|
||||
fi un apel pozitional care specifica deja *mai multi* parametri decat lista curenta** (nu e cazul
|
||||
gasit) sau un apel care se opreste inainte de un parametru fara `DEFAULT` (nu e cazul: ambele
|
||||
proceduri `adauga_articol_factura` si `adauga_articol_factura_deviz` au deja tiparul "coada de
|
||||
parametri opsionali cu `DEFAULT NULL`" folosit activ de ROAGEST si ROAAUTO astazi — precedent direct
|
||||
ca acest tipar de extensie e deja tolerat de codul existent).
|
||||
|
||||
### 1.2 `scrie_factura2` / `scrie_factura`
|
||||
|
||||
Semnatura curenta (linia 640): 17 parametri, **fara niciun `DEFAULT`** — `V_TOTFTVA`...
|
||||
`V_PARAMETRU_ADITIONAL` (15 IN), `V_ID_VANZARE` (OUT), `V_CURSOR_VERIFICARE` (OUT, ultimul).
|
||||
|
||||
| produs | fisier:linie | nr. parametri trimisi | mod |
|
||||
|---|---|---|---|
|
||||
| ROAFACTURARE (COMUN) | `COMUN\clase\ofacturare.vc2:14345` (+ al doilea sit `:18343`) | **16 din 17** — se opreste la `V_ID_VANZARE` (`?@poDate.nid_vanzare`), omite `V_CURSOR_VERIFICARE` | pozitional |
|
||||
| ROACONT/ROAGEST/ROAACNPRO/ROACONTRACTE (COMUN, grup identic) | `COMUN\clase\ofacturare.vc2:14130` (+ `:18124`) | 16 din 17, idem | pozitional |
|
||||
| ROAIMOB (COMUN) | `COMUN\clase\ofacturare.vc2:14124` (+ `:18118`) | 16 din 17, idem | pozitional |
|
||||
| ROAAUTO (COMUN) | `COMUN\clase\ofacturare.vc2:14129` (+ `:18151`) | 16 din 17, idem | pozitional |
|
||||
| ROAGEST (implementare proprie) | `Programe\ofactureaza.prg:307` | 16 din 17, idem (`?poDate.nid_vanzare` e ultimul bind) | pozitional |
|
||||
|
||||
**Anomalie semnalata, in afara scopului cerut dar relevanta pentru risc general pe pachet**:
|
||||
toate sitele active de apel pentru `scrie_factura2` — in toate cele 7 produse, in ambele
|
||||
implementari (COMUN si ROAGEST proprie) — transmit **16 din cele 17 parametri declarati**,
|
||||
omitand complet ultimul parametru `V_CURSOR_VERIFICARE OUT pack_facturare.cursor_facturare`, care
|
||||
**nu are `DEFAULT`** (parametrii `OUT` nu pot avea `DEFAULT` in PL/SQL). Verificat identic in
|
||||
ambele exporturi disponibile (`ff_2026_08_06_10` si `ff_2026_08_09_01`), deci nu e o modificare de
|
||||
ultima ora. **NEVERIFICAT** cum functioneaza efectiv acest apel in productie (nu am acces la DB
|
||||
live) — posibile explicatii: (a) `goExecutor.oExecute(lcSql, lcCursorVerificare)` are o logica
|
||||
proprie de completare/legare a cursorului de iesire care nu se vede din text, (b) parametrul are
|
||||
de fapt un comportament tolerat de driver-ul Oracle folosit, sau (c) e un bug preexistent,
|
||||
netestat de multa vreme pe acest cod-cale. Nu are legatura cu schimbarea propusa la
|
||||
`contabilizeaza_articol` (nu se ating parametrii lui `scrie_factura2`), dar merita un test manual
|
||||
separat inainte de a presupune ca "toate caile prin pachet functioneaza azi fara eroare".
|
||||
|
||||
### 1.3 `scrie_nota` / `scrie_nota_import` — fals pozitiv de cautare
|
||||
|
||||
`scrie_nota_import` gasit in ROACONT (`Clase\oactualizari.vc2:920`, `Programe\ocont2003.prg:785`,
|
||||
`Programe\oproceduri_actualizari.prg:189`, `Programe\oproceduri_inchidere.prg:167,335,524`) si in
|
||||
ROAGEST (`Programe\inchidere_k.prg:192,257,373,381`) **NU este** functia `pack_facturare.scrie_nota`
|
||||
din pachet — e o **procedura VFP locala**, definita in
|
||||
`ROAFACTURARE\COMUN\programe\ooperatii_comune.prg:1081` (`PROCEDURE scrie_nota_import(...)`),
|
||||
folosita pentru import de note contabile, fara nicio legatura cu `PACK_FACTURARE`. Cautarea directa
|
||||
`pack_facturare\.scrie_nota\b` in tot arborele VFP a dat **zero rezultate** (sectiunea 0).
|
||||
|
||||
### 1.4 `descarca_gestiune`
|
||||
|
||||
Zero apeluri externe gasite in cod VFP (cautare directa `pack_facturare\.descarca_gestiune`,
|
||||
0 rezultate in toate cele 7 produse). Pachetul are doua supraincarcari (liniile 752 si 773),
|
||||
probabil folosite doar intern (apelate din `contabilizeaza_articol` conform comentariului de la
|
||||
linia 1466: `-- facturare_articole2, contabilizeaza_articol > descarca_gestiune`) —
|
||||
**NEVERIFICAT** linie-cu-linie in restul corpului pachetului (17217 linii; nu am parcurs tot
|
||||
corpul, doar semnaturile si comentariile relevante), dar nimic din cautarea externa VFP le atinge.
|
||||
|
||||
## 2. Duplicarea fisierelor `COMUN\`
|
||||
|
||||
Comparatie pe continut (MD5), nu pe nume, pentru cele 3 fisiere unde s-au gasit apeluri, in cele
|
||||
7 copii `COMUN\` (ROAFACTURARE, ROACONT, ROAGEST, ROAIMOB, ROAACNPRO, ROACONTRACTE, ROAAUTO):
|
||||
|
||||
| fisier | rezultat |
|
||||
|---|---|
|
||||
| `clase\ofacturare.vc2` | **4 variante distincte**: ROAFACTURARE unic; {ROACONT, ROAGEST, ROAACNPRO, ROACONTRACTE} identice intre ele; ROAIMOB unic; ROAAUTO unic |
|
||||
| `programe\ofacturare_stoc.prg` | **identic byte-cu-byte in toate cele 7** (un singur MD5) |
|
||||
| `programe\ooperatii_comune.prg` | identic in 6 din 7; **ROAIMOB are o varianta diferita** |
|
||||
|
||||
Pentru `ofacturare.vc2`, desi hash-urile difera intre cele 4 grupuri (fisierul are ~19000 de linii
|
||||
si diferente in alte zone), **structura si continutul exact al apelurilor catre
|
||||
`adauga_articol_factura` / `scrie_factura2` sunt identice cuvant-cu-cuvant** intre toate cele 4
|
||||
variante — doar offset-ul de linie difera (ex. apelul `adauga_articol_factura` e la linia 14069 in
|
||||
ROAFACTURARE, 13853 in grupul ROACONT/ROAGEST/ROAACNPRO/ROACONTRACTE, 13847 in ROAIMOB, 13853 in
|
||||
ROAAUTO — text identic, verificat prin grep pe fiecare variant separat). Deci pentru scopul acestei
|
||||
schimbari: **e acelasi sit de apel duplicat de 7 ori** (nu 7 implementari independente care ar
|
||||
putea diverge in reactia la parametri noi), plus doua implementari suplimentare, independente si
|
||||
reale, in ROAGEST (`Programe\ofactureaza.prg`) si ROAACNPRO/ROAAUTO
|
||||
(`Programe\proceduri_acnpro.prg` / `Programe\oproceduri_devize.prg`, pentru varianta `_deviz`).
|
||||
|
||||
`ofacturare_stoc.prg` (contine apelul catre `adauga_articol_factura_stoc`) e literalmente **acelasi
|
||||
fisier**, deci 1 singur loc de verificat, nu 7.
|
||||
|
||||
## 3. Cine emite efectiv facturi prin acest pachet
|
||||
|
||||
Pe baza apelurilor gasite (nu doar a prezentei fisierului `COMUN\` — care exista in toate 7, dar
|
||||
nu inseamna ca produsul chiar il foloseste activ pentru facturare):
|
||||
|
||||
| produs | ajunge la `pack_facturare` pentru facturare? | dovada |
|
||||
|---|---|---|
|
||||
| ROAFACTURARE | **Da** | `adauga_articol_factura` + `scrie_factura2` in `COMUN\clase\ofacturare.vc2` |
|
||||
| ROACONT | **Da** | acelasi cod COMUN (grup identic) |
|
||||
| ROAGEST | **Da**, cu 2 cai | codul COMUN identic + implementare proprie in `Programe\ofactureaza.prg` (posibil cod mai vechi/alternativ, coexista) |
|
||||
| ROAIMOB | **Da** | varianta proprie de `ofacturare.vc2`, dar acelasi tipar de apel |
|
||||
| ROAACNPRO | **Da**, cu 2 cai | codul COMUN (grup identic) + `adauga_articol_factura_deviz` in `Programe\proceduri_acnpro.prg` |
|
||||
| ROACONTRACTE | **Da** | codul COMUN (grup identic) — nu are cale proprie suplimentara gasita |
|
||||
| ROAAUTO | **Da**, cu 2 cai | varianta proprie de `ofacturare.vc2` + `adauga_articol_factura_deviz` in `Programe\oproceduri_devize.prg` |
|
||||
|
||||
Niciun produs din cele 7 nu pare sa aiba `COMUN\clase\ofacturare.vc2` ca fisier mort — toate cele
|
||||
7 au si `Programe\` propriu (in afara de COMUN) care instantiaza clasa relevanta (NEVERIFICAT
|
||||
exhaustiv ca fiecare produs chiar *instantiaza si ruleaza* clasa din `ofacturare.vc2` la runtime —
|
||||
verificarea s-a facut pe prezenta apelului in sursa, nu pe flux de executie live). **Toate cele 7
|
||||
produse sunt in aria de regresie** pentru orice modificare de pachet care afecteaza
|
||||
`adauga_articol_factura*` sau `scrie_factura2` — nu doar ROAFACTURARE.
|
||||
|
||||
## 4. Verdict de risc
|
||||
|
||||
**Pentru `contabilizeaza_articol` insusi** (obiectul cerut al schimbarii): risc **practic zero**.
|
||||
Cei 3 apelanti sunt toti interni pachetului, toti pozitionali cu un singur argument identic cu
|
||||
semnatura actuala (`detalii_articol VANZARI_DETALII_TEMP%ROWTYPE`). Adaugarea de parametri noi
|
||||
`DEFAULT NULL` la coada nu schimba niciunul dintre aceste 3 apeluri — nu necesita nicio modificare
|
||||
in cele 3 sit-uri, indiferent de produs. Nu exista niciun apel extern (nici alt pachet PL/SQL, nici
|
||||
VFP din niciun produs) care sa poata fi afectat, pentru ca nu exista niciun apel extern, punct.
|
||||
|
||||
**Pentru pachetul `PACK_FACTURARE` in ansamblu**, daca schimbarea reala atinge si alte proceduri
|
||||
publice (nu doar `contabilizeaza_articol`):
|
||||
- Tiparul "adauga parametri `DEFAULT NULL` la coada, apelantii pozitionali raman neschimbati"
|
||||
**are deja precedent activ** in codul curent: `adauga_articol_factura` (V_TAXCODE, V_LOT) si
|
||||
`adauga_articol_factura_deviz` (4 parametri finali cu DEFAULT) sunt deja apelate de unii
|
||||
producatori omitand parametrii finali optionali (ROAGEST, ROAAUTO). Deci genul de schimbare
|
||||
propus e sigur *daca* regula "toti parametrii noi sunt strict la coada si toti au DEFAULT" se
|
||||
respecta — ceea ce e exact ipoteza declarata pentru `contabilizeaza_articol`.
|
||||
- **Singurul risc real identificat in tot pachetul** nu vine din schimbarea propusa, ci e o
|
||||
anomalie preexistenta, independenta: apelurile la `scrie_factura2` (in toate cele 7 produse) omit
|
||||
deja parametrul final `V_CURSOR_VERIFICARE` (OUT, fara DEFAULT posibil). Daca cineva "repara" acea
|
||||
nepotrivire ca parte a aceleiasi lucrari de mentenanta pe pachet, *acolo* ar trebui atinsi toti
|
||||
apelantii din sectiunea 1.2 — dar asta e o schimbare diferita de cea descrisa (parametri noi la
|
||||
`contabilizeaza_articol`), nesolicitata explicit aici. O semnalez ca sa nu fie confundata cu
|
||||
"toti apelantii raman neschimbati" pentru intregul pachet, daca scopul lucrarii se extinde.
|
||||
- Nu s-a gasit niciun apel cu named notation (`=>`) nicaieri in cele 7 produse pentru functiile
|
||||
cerute — deci nu exista risc de reordonare-parametri-pe-nume la adaugarea de parametri noi.
|
||||
|
||||
**Lista de produse pe care trebuie rulata regresia** (din sectiunea 3): toate cele 7 —
|
||||
ROAFACTURARE, ROACONT, ROAGEST, ROAIMOB, ROAACNPRO, ROACONTRACTE, ROAAUTO — pentru ca toate emit
|
||||
efectiv facturi prin acest pachet, chiar daca modificarea la `contabilizeaza_articol` insusi nu
|
||||
impune, teoretic, nicio schimbare de cod la niciunul dintre ele.
|
||||
250
docs/cercetare/verif_baza_vie_cont_venit.md
Normal file
250
docs/cercetare/verif_baza_vie_cont_venit.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# Verificari pe baza de date vie — reteta contului de venit (#13, J-quater)
|
||||
|
||||
Rulat: **10.08.2026**, schema `MARIUSM_AUTO` pe `ROA_CENTRAL` (`10.0.20.121:1521`, `SERVICE_NAME=ROA`),
|
||||
client `D:\ROA\instantclient_19_18\sqlplus.exe`. **Doar `SELECT`** — nicio scriere, nicio modificare de
|
||||
structura. Raspunde la punctul 2 din „Ce ramane deschis" al `docs\handoff_13_formular_unificat.md`.
|
||||
|
||||
## Verdict, in trei randuri
|
||||
|
||||
**Pasul 1 al retetei (derivarea `SCC`) e sigur: zero ambiguitate, zero cazuri de cont de venit gol pe
|
||||
articole reale.** **Pasul 2 (gasirea unei politici cu acel `SCC`) reuseste pentru 3 din 7 conturi
|
||||
candidate si eșueaza pentru 4** — dar nu asa cum prevedea planul: **`704` are 19 politici valabile azi,
|
||||
nu zero**, iar cele care lipsesc sunt `702`, `703`, `711`, `7018`. **Riscul serios nu e ambiguitatea, ci
|
||||
`PTVA`-ul notei** — singura nota cu `SCC=707` are `PTVA=5`, deci alegerea politicii dupa `SCC` importa si
|
||||
o cota de TVA care poate fi greșita.
|
||||
|
||||
## Doua corectii la planul si handoff-ul rundei 8
|
||||
|
||||
| Ce spune planul (J-quater, „Costurile") | Ce arata baza vie |
|
||||
|---|---|
|
||||
| „pentru **704** nu s-a gasit nicio nota configurata, deci trebuie creata o data din ecranul de configurare note contabile" | **FALS.** `704` e cel mai bine acoperit cont: **4 note de vanzari** (`NOTA 1`, `COMISION INTERMEDIERE`, `SERVICII VALUTA`, `SERVICII TRANSPORT`) si **25 de politici** (19 valabile azi). `NOTA 1` — nota implicita a majoritatii politicilor — are exact `SCC=704`. **Pasul de creare a notei pentru 704 se scoate din plan.** |
|
||||
| „`CONT_VENIT` are date reale (**20 de conturi**, migrare 2023)" | **Imprecis.** Tabelul are **41 de randuri active**; exact **20** au `CONT_VENIT` populat. Restul 21 sunt conturile de ajustari (`391`…`398`, toate cu `CONT_CHELT=681`) — vezi mai jos de ce nu conteaza. |
|
||||
|
||||
Ce **se confirma** din plan: `CONT_VENIT` **n-are consumatori** (nu am verificat aici — a fost stabilit pe
|
||||
cod in runda 8); lantul invers e realizabil; ambiguitatea „mai multe politici cu acelasi `SCC`" e reala.
|
||||
|
||||
## 1. DDL-ul lui `CORESP_CONT_VENCHELT` (cerut explicit in handoff)
|
||||
|
||||
| # | Coloana | Tip | Null |
|
||||
|---|---|---|---|
|
||||
| 1 | `ID_CCV` | `NUMBER(10,0)` | N |
|
||||
| 2 | `CONT` | `VARCHAR2(4)` | Y |
|
||||
| 3 | `CONT_CHELT` | `VARCHAR2(4)` | Y |
|
||||
| 4 | `CONT_VENIT` | `VARCHAR2(4)` | Y |
|
||||
| 5 | `STERS` | `NUMBER(1,0)` | N |
|
||||
| 6 | `DATAORAS` | `DATE` | Y |
|
||||
| 7 | `CONT_APROVIZIONARE` | `VARCHAR2(4)` | Y |
|
||||
| 8 | `CONT_DIFERENTE` | `VARCHAR2(4)` | Y |
|
||||
|
||||
Constrangeri: **doar** `PK_CORESP_CONT_VENCHELT` pe `ID_CCV`, plus `NOT NULL` pe `ID_CCV` si `STERS`.
|
||||
**Nu exista unique pe `CONT`** — deci nimic in schema nu impiedica doua randuri pe acelasi cont de
|
||||
gestiune. In date insa **nu exista niciun `CONT` duplicat**, deci pasul 1 al retetei e determinist azi.
|
||||
Fragilitatea e de tip „merge pana nu merge": daca cineva adauga un al doilea rand pe `301`, derivarea
|
||||
devine ambigua fara ca nimic sa semnaleze.
|
||||
|
||||
## 2. Continutul relevant al tabelului — cele 20 de randuri cu cont de venit
|
||||
|
||||
| `CONT` (gestiune) | `CONT_CHELT` | **`CONT_VENIT`** |
|
||||
|---|---|---|
|
||||
| `231` | `212` | `707` |
|
||||
| `301`, `302`, `3021`–`3028`, `303` | `601`, `602`, `6021`–`6028`, `603` | **`707`** (13 randuri) |
|
||||
| `331`, `332` | `711` | `711` |
|
||||
| `341` | `711` | `702` |
|
||||
| `345`, `348` | `711` | `7015` |
|
||||
| `346` | `711` | `703` |
|
||||
| `361` | `711` | `7018` |
|
||||
| `371` | `607` | `707` |
|
||||
| `381` | `608` | `707` |
|
||||
|
||||
Cele 21 de randuri fara `CONT_VENIT` sunt `391`, `392`, `3921`, `3922`, `393`, `394`, `3941`, `3945`,
|
||||
`3946`, `395`, `3951`–`3958`, `396`, `397`, `398` — toate cu `CONT_CHELT=681`, adica **ajustari pentru
|
||||
depreciere**, nu conturi de gestiune pe care sta marfa.
|
||||
|
||||
**Si contează**: `articole_pe_cont_cu_venit_gol = 0`. Niciun articol activ din `NOM_ARTICOLE` nu are
|
||||
`CONT`-ul pe un rand cu `CONT_VENIT` gol. **Deci ramura „cont de venit derivat gol" nu are cazuri reale**
|
||||
— trebuie tratata defensiv, dar nu e un scenariu de acoperit in UX.
|
||||
|
||||
## 3. Interogarea inversa — rezultatul pe fiecare `SCC` candidat
|
||||
|
||||
```sql
|
||||
with cand as (select column_value scc from table(sys.odcivarchar2list('707','711','702','703','7015','7018','704')))
|
||||
select c.scc, count(distinct p.id_pol) pol,
|
||||
count(distinct case when nvl(p.datai,date '1900-01-01')<=trunc(sysdate)
|
||||
and nvl(p.datas,date '2999-12-31')>=trunc(sysdate)
|
||||
then p.id_pol end) pol_valabile_azi,
|
||||
count(distinct nc.id_set) seturi, count(nc.id_note) nc_randuri
|
||||
from cand c
|
||||
left join note_contabile nc on nc.scc = c.scc
|
||||
left join crm_note_vanzari nv on nv.id_set = nc.id_set and nvl(nv.sters,0)=0
|
||||
left join crm_politici_preturi p on p.id_nota = nv.id_nota and p.sters=0
|
||||
group by c.scc;
|
||||
```
|
||||
|
||||
| `SCC` | Politici | **Valabile azi** | Seturi cu acest `SCC` | Randuri `NOTE_CONTABILE` | Verdict pentru pasul 2 |
|
||||
|---|---|---|---|---|---|
|
||||
| **`704`** | 25 | **19** | 7 | 28 | **reuseste**, cu ambiguitate mare |
|
||||
| **`707`** | 4 | **4** | 5 | 8 | **reuseste**, ambiguitate mica |
|
||||
| **`7015`** | 1 | **1** | 1 | 1 | **reuseste, caz ideal** |
|
||||
| `711` | 0 | 0 | 4 | 4 | **eșuează** — note exista, dar **nicio politica** nu le foloseste |
|
||||
| `702` | 0 | 0 | 3 | 3 | **eșuează** — idem |
|
||||
| `703` | 0 | 0 | 3 | 3 | **eșuează** — idem |
|
||||
| `7018` | 0 | 0 | **0** | **0** | **eșuează total** — nu exista nici macar nota |
|
||||
|
||||
Citit pe conturile de gestiune: reteta merge pentru marfa (`301`–`303`, `371`, `381`, `231` → `707`) si
|
||||
pentru produsele din `345`/`348` (→ `7015`), plus pentru orice cade pe fallback-ul `704`. **Nu merge**
|
||||
pentru `331`/`332` (→ `711`), `341` (→ `702`), `346` (→ `703`), `361` (→ `7018`) — adica **producția
|
||||
neterminată, semifabricatele, produsele reziduale si ambalajele**.
|
||||
|
||||
Distinctia care conteaza la `711`/`702`/`703`: **nota exista, doar politica lipseste.** Costul de
|
||||
configurare e „ataseaza nota existenta unei politici", nu „creeaza nota de la zero". Doar `7018` cere
|
||||
si nota noua.
|
||||
|
||||
## 4. Riscul pe care planul nu l-a vazut: `PTVA` si `IN_VALUTA` de pe nota
|
||||
|
||||
Cele 7 note de vanzari active, cu nota contabila atasata:
|
||||
|
||||
| `ID_NOTA` | Denumire | `ID_SET` | `SCD` | **`SCC`** | **`PTVA`** | `CU_TVA` | **`IN_VALUTA`** |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 1 | `NOTA 1` | 251023 | `4111` | **704** | **21** | 1 | 0 |
|
||||
| 2 | `DISCOUNT` | 251024 | `667` | 4111 | 21 | 1 | 0 |
|
||||
| 3 | `COMISION INTERMEDIERE` | 251025 | `4111` | **704** | **0** | 1 | **1** |
|
||||
| 4 | `SERVICII VALUTA` | 251026 | `4111` | **704** | **0** | 1 | **1** |
|
||||
| 5 | `VANZARE MARFA` | 251027 | `4111` | **707** | **5** | 1 | 0 |
|
||||
| 6 | `PRODUCTIE` | 251028 | `4111` | **7015** | 21 | 1 | 0 |
|
||||
| 7 | `SERVICII TRANSPORT` | 251029 | `4111` | **704** | 21 | 1 | 0 |
|
||||
|
||||
> **RETRAS — verificat pe cod si infirmat.** Ingrijorarea de mai jos, formulata la prima citire a acestor
|
||||
> date („`PTVA=5` pe singura nota cu `SCC=707` produce TVA greșit"), **nu se susține**.
|
||||
> `docs\cercetare\s10_pret_rederivat.md`, „Completare: nota contabila a politicii" p. 2: `cursor_articol`
|
||||
> (`PACK_FACTURARE:7218-7271`) **nu selecteaza `D.PTVA`**; cota folosita e
|
||||
> `detalii_articol.proc_tvav * 100 - 100` (`:7464`), din articol / document. Coloana e moarta pentru
|
||||
> `contabilizeaza_articol`. Iar `IN_VALUTA=1` pe un document in lei nu da eroare si nu strica suma —
|
||||
> doar populeaza redundant `ACT_TEMP.SUMA_VAL` la curs 1. **Deci criteriul de selectie NU trebuie extins
|
||||
> cu `PTVA` / `IN_VALUTA`.** Se pastreaza textul de mai jos doar ca urma a raționamentului; nu se citeaza
|
||||
> ca risc.
|
||||
|
||||
Ce **rămâne** ca risc real din aceste date, si e mai grav: `cursor_articol` face `LEFT JOIN
|
||||
NOTE_CONTABILE ON C.ID_SET = D.ID_SET` fara `ROWNUM` si fara agregare, iar bucla care il consuma executa
|
||||
`scrie_nota` **si** `descarca_gestiune` o data **pentru fiecare rand al setului**, cu pretul si cantitatea
|
||||
intregi de fiecare data. Deci un set cu N randuri = **venit inregistrat de N ori si gestiune descarcata de
|
||||
N ori**. Cele 7 note de vanzari active au exact un rand fiecare — dar **30 din cele 40 de seturi din baza
|
||||
au mai multe, cu maxim 30**. Vezi punctul 6.1 mai jos.
|
||||
|
||||
Ce planul enunta drept **avantaj** al retetei — „politica reala aduce si `CU_TVA`, `IN_VALUTA`,
|
||||
`EXPLICATIE`, `ASCD`, `ASCC`" — se confirma ca argument, cu nuanta ca `ASCD`/`ASCC` au oricum fallback
|
||||
automat din grupul de utilizatori, deci nu ele justifica alegerea.
|
||||
|
||||
Textul original al ingrijorarii, pastrat pentru trasabilitate:
|
||||
|
||||
- **`707` are o singura nota, si aceea cu `PTVA=5`.** Pentru marfa (majoritatea articolelor de gestiune)
|
||||
singurul rezultat posibil al pasului 2 e nota cu TVA 5%.
|
||||
- **Doua din cele patru note cu `SCC=704` au `IN_VALUTA=1`** (`SERVICII VALUTA`, `COMISION INTERMEDIERE`),
|
||||
deci o potrivire pe `SCC` poate ateriza pe ele pe un document in lei.
|
||||
|
||||
Colateral util pentru **S4c**: exista deja o nota de discount configurata — `id_nota=2`, `SCD=667` /
|
||||
`SCC=4111`. Nu e o eroare de configurare (explica randul `SCC=4111` din agregat), e nota folosita pentru
|
||||
discount. De verificat la S4c daca discountul pe linie trece prin ea.
|
||||
|
||||
## 5. Efectul colateral al pasului 3 — politica **este** o lista de preturi
|
||||
|
||||
Pasul 3 al retetei insereaza articolul in politica alesa, prin `pack_preturi.adauga_politica_pret_art`.
|
||||
Politicile candidate, cu numarul de articole pe care le contin azi:
|
||||
|
||||
| `SCC` | `ID_POL` | Nume | Articole |
|
||||
|---|---|---|---|
|
||||
| `7015` | 41 | `STOC PRODUSE` | **6310** |
|
||||
| `707` | 39 | `LISTA PRETURI LEI` | **909** |
|
||||
| `707` | 40 | `LISTA PRETURI CU TVA EURO` | 2 |
|
||||
| `707` | 47 / 62 | `DEPOZIT ELI` / `DEPOZIT ELI_31/07/2025 11:07:25` | 3 / 3 |
|
||||
| `704` | 2 | `LISTA 2` | 19 |
|
||||
| `704` | 34 | `LISTA 2 - EURO` | 12 |
|
||||
| `704` | 36 | `SERVICE AUTO` | 9 |
|
||||
| `704` | 35 | `LISTA STANDARD EURO` | 8 |
|
||||
| `704` | 9 | `CHIOSC` | 5 |
|
||||
| `704` | 6, 12, 13, 14, 15, 16 | `LISTA DE PROBA`, `S5 ZILNIC`, `S6 WEEKEND SEZON`, `S7 CRACIUN`, `S3 PASTE`, `S3 1MAI` | 4 fiecare |
|
||||
| `704` | 65, 66 | `TRANSPORT`, `SERVICII INFORMATICE` | 1 fiecare |
|
||||
| `704` | 26, 27, 28, 29, 30, 31 | `S6 ZILE IARNA`, `S8 REVELION`, `S1 IARNA2`, `S1 IARNA2 WEEKEND`, `S2 PRIMAVARA`, `S7 CRACIUN` | **0** |
|
||||
|
||||
**Astea sunt liste de preturi reale, cu nume de client si de sezon.** Daca reteta alege `LISTA PRETURI
|
||||
LEI` sau `STOC PRODUSE` pentru un articol adaugat ad-hoc pe factura, articolul **apare de acum in lista
|
||||
de preturi a acelui context**, cu pretul cu care a fost facturat o data. Asta e efectul pe care planul il
|
||||
descrie drept „politica tehnica populata automat" — dar **numai daca politica e chiar tehnica**.
|
||||
Alegerea unei politici comerciale existente **poluează o lista de preturi de producție**.
|
||||
|
||||
Consecinta de proiectare, de dus in J-quater: pasul 2 nu trebuie sa caute *orice* politica cu `SCC`-ul
|
||||
potrivit, ci **o politica tehnica dedicata, una per `SCC`**, creata anume — modelul
|
||||
`gnId_pol_pret_stoc` extins de la o politica la sapte. Cele sase politici cu **0 articole** arata ca o
|
||||
politica goala e o stare acceptata de produs.
|
||||
|
||||
Detaliu care ajuta: **majoritatea politicilor cu `SCC=704` trimit la aceeasi `id_nota=1`**. Deci
|
||||
ambiguitatea celor 19 politici e ambiguitate de **lista de preturi**, nu de rezultat contabil — toate dau
|
||||
acelasi `704` / `PTVA=21` / lei. Cu atat mai mult, criteriul de alegere trebuie sa fie „care lista de
|
||||
preturi vreau sa murdaresc", si raspunsul corect e „niciuna dintre cele existente".
|
||||
|
||||
## 6. Fragilitati masurate, de trecut in Riscuri
|
||||
|
||||
1. **`ID_SET` cu mai multe randuri dubleaza venitul si descarcarea de gestiune.** Pe cele 7 seturi
|
||||
folosite de politici, fiecare are exact **1** rand `NOTE_CONTABILE`. Dar in tabel sunt **40 de seturi,
|
||||
din care 30 au mai multe randuri**, cu maxim **30 de randuri pe set**. Confirmat pe cod
|
||||
(`s10_pret_rederivat.md`, Completare p. 1): `cursor_articol` face `LEFT JOIN ... ON C.ID_SET =
|
||||
D.ID_SET` **fara `ROWNUM`, fara agregare, fara filtru**, iar bucla `:7393-7542` executa `scrie_nota`
|
||||
**si** `descarca_gestiune` o data per rand, **cu aceeasi cantitate si acelasi pret intreg**, fara
|
||||
distributie (`ORDINE` nu apare in tot pachetul). Deci pasul 2 al retetei trebuie sa garanteze **un
|
||||
singur rand pe setul ales**, nu doar un rand cu `SCC`-ul potrivit. Conditia care face reteta sigura
|
||||
azi e o coincidenta a datelor, nu o garanție a codului sau a schemei.
|
||||
2. **Doua politici active fara `ID_NOTA`**: `32 HOTEL TAXE` si `33 HOTEL CAZARE` (ambele valabile
|
||||
01.01.2010–31.12.2099, `id_valuta=3`). Un articol pe una din ele are `id_pol` valid dar **fara nota** —
|
||||
comportament neverificat, intrebare trimisa agentului pe `PACK_FACTURARE`. E o gaura care **exista
|
||||
deja azi**, independenta de #13.
|
||||
3. **Fara unique pe `CORESP_CONT_VENCHELT.CONT`** — vezi punctul 1.
|
||||
|
||||
## 7. Distributia articolelor — cat de mult conteaza fiecare ramura a deciziei 27
|
||||
|
||||
`NOM_ARTICOLE`, 6430 articole active:
|
||||
|
||||
| Prima cifra din `CONT` | Articole | Conturi distincte | Ramura deciziei 27 |
|
||||
|---|---|---|---|
|
||||
| `3xx` | **6125** | 9 | gestionabil → `CORESP_CONT_VENCHELT` |
|
||||
| `8xx` (toate `8035`) | 26 | 1 | nu e 6xx/7xx, nu e in `CORESP` → **`704`** |
|
||||
| `6xx` | 22 | 5 | `NOM_ARTICOLE.CONT` ca atare |
|
||||
| `7xx` | 20 | 6 | `NOM_ARTICOLE.CONT` ca atare |
|
||||
| `1xx` | 4 | 2 | → `704` |
|
||||
| `4xx` | 3 | 2 | → `704` |
|
||||
| `0xx` | 1 | 1 | → `704` |
|
||||
| `2xx` | 1 | 1 | → `704` |
|
||||
| **fara `CONT`** | **228** | — | → `704` |
|
||||
|
||||
Conturi de pe articole care **nu** sunt 6xx/7xx si **nu** apar in `CORESP_CONT_VENCHELT.CONT`, deci cad pe
|
||||
`704`: `8035` (26 articole), `123` (3), `446` (2), `111`, `409`, `0`, `212`, `37` (1 fiecare).
|
||||
|
||||
**Ramura dominanta e de departe cea gestionabila** — 6125 din 6430. Iar ea trimite in majoritate spre
|
||||
`707`, adica spre cazul cu `PTVA=5`. Cele 228 de articole fara cont si cele 26 pe `8035` fac fallback-ul
|
||||
`704` un caz real, nu teoretic — si acolo reteta functioneaza.
|
||||
|
||||
Nota: `212` apare pe un articol, dar in `CORESP_CONT_VENCHELT` figureaza ca `CONT_CHELT` (pe randul
|
||||
`231`), nu ca `CONT`. Deci cade corect pe `704` prin regula deciziei 27.
|
||||
|
||||
## Ce nu s-a putut stabili aici, si de ce
|
||||
|
||||
- **Ce face `contabilizeaza_articol` cu `PTVA` / `CU_TVA` / `IN_VALUTA` ale notei** — e intrebare de cod,
|
||||
nu de date. Trimisa agentului deja incarcat in `PACK_FACTURARE`; raspunsul intra in
|
||||
`docs\cercetare\s10_pret_rederivat.md`, sectiunea „Completare: nota contabila a politicii". **Pana
|
||||
atunci pasul 2 al retetei nu se considera validat.**
|
||||
- **Daca `CONT_VENIT` chiar n-are consumatori** — stabilit pe cod in runda 8, nu reverificat pe date.
|
||||
- **Comportamentul politicii fara nota** — idem, intrebare de cod, trimisa.
|
||||
- Verificarile sunt pe **schema de dezvoltare** `MARIUSM_AUTO`. Datele sunt reprezentative (liste de
|
||||
preturi cu nume reale, 6430 articole), dar **o baza de client poate avea alta configurare de note** —
|
||||
in special poate avea deja politici pe `711` / `702` / `703`. Concluziile pe **structura** (lantul,
|
||||
absenta unique-ului, `ID_SET` multi-rand) sunt insa independente de date.
|
||||
|
||||
## Reproducere
|
||||
|
||||
Cele trei scripturi rulate, in ordine: descoperire DDL/coloane · interogarea inversa pe `SCC` ·
|
||||
efecte colaterale si distributii. Interogarea-cheie e citata integral la punctul 3. Rulare:
|
||||
|
||||
```powershell
|
||||
& 'D:\ROA\instantclient_19_18\sqlplus.exe' -L -S 'MARIUSM_AUTO/ROMFASTSOFT@ROA_CENTRAL' '@fisier.sql'
|
||||
```
|
||||
|
||||
SQL-ul se scrie in fisier **ASCII**, nu prin pipe din PowerShell (BOM-ul UTF-16 sparge parserul), si cu
|
||||
`linesize 32767` — vezi `COMUN\docs\oracle_export.md`.
|
||||
325
docs/cercetare/verif_goluri_ntip_aviz.md
Normal file
325
docs/cercetare/verif_goluri_ntip_aviz.md
Normal file
@@ -0,0 +1,325 @@
|
||||
# Verificare adversariala — golul ntip=4 si "ramurile de aviz" (contabilizeaza_articol)
|
||||
|
||||
Verifica raportul `docs/cercetare/gol_ntip4_factura_din_avize.md`. Rol: incercare de infirmare,
|
||||
nu confirmare. Toate citatele de mai jos sunt re-verificate direct in aceasta runda pe
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (17217 linii) si
|
||||
pe `COMUN\clase\ofacturare.vc2` (ambele copii, `frm_facturare_articole` ~13967-14500 si
|
||||
`frm_facturare_articole2` ~18003-18500). Status: **TERMINAT**.
|
||||
|
||||
## Corectie structurala prealabila — cei "3 apelanti" ai `contabilizeaza_articol` sunt gresit numiti
|
||||
|
||||
Toate rapoartele anterioare (`canal_cont_venit_fara_politica.md:67`, `nota_contabila_fara_politica.md:103`,
|
||||
si implicit raportul verificat) numesc cei 3 apelanti interni `scrie_factura2`,
|
||||
**`scrie_factura_avize_retur`**, `scrie_aviz_retur`. **Gresit pentru al doilea.** Verificare directa
|
||||
pe granitele reale (`PROCEDURE ...` / `END ...`):
|
||||
|
||||
```
|
||||
ff_...:6264 PROCEDURE scrie_factura_avize_retur(... ff_...:6657 END scrie_factura_avize_retur;
|
||||
ff_...:6692 PROCEDURE scrie_factura_avize(... ff_...:7058 END scrie_factura_avize;
|
||||
ff_...:7085 PROCEDURE scrie_aviz_retur(... ff_...:7171 END scrie_aviz_retur;
|
||||
```
|
||||
|
||||
Apelul de la `ff_...:6858` (`pack_facturare.contabilizeaza_articol(tab_detalii(i));`, in interiorul
|
||||
`IF articole_aviz(j).custodie = 1 THEN`) cade **intre 6692 si 7058** — deci apartine lui
|
||||
**`scrie_factura_avize`** (fara `_retur`), nu lui `scrie_factura_avize_retur` (care se termina la
|
||||
6657, cu 200+ linii inainte). `scrie_factura_avize_retur` **nu cheama deloc**
|
||||
`contabilizeaza_articol` — corpul ei (6264-6657) insereaza direct in `VANZARI_DETALII_TEMP` din
|
||||
`VANZARI_DETALII` (randurile avizului sursa deja existente), fara sa treaca prin
|
||||
`adauga_articol_factura` sau `contabilizeaza_articol`.
|
||||
|
||||
**De ce conteaza**: `scrie_factura_avize` e exact handler-ul Oracle pentru `ntip=4` ("facturare din
|
||||
aviz") — apelat din VFP la `ofacturare.vc2:14318` (`Case poDate.Tip = 4`). Deci al doilea apel real
|
||||
catre `contabilizeaza_articol` **e in interiorul propriului handler `ntip=4`** (pentru randurile
|
||||
`custodie=1`), nu intr-un flux separat "avize+retur". Cei 3 apelanti reali sunt:
|
||||
|
||||
| Apel | Procedura reala | Cand se ajunge la ea din VFP |
|
||||
|---|---|---|
|
||||
| `ff_...:6141` | `scrie_factura2` | `Case Inlist(poDate.Tip,3,21,25,28,42,47)` sau `Otherwise` (`ofacturare.vc2:14332,14361`, identic la `:18330,18361`) — calea principala pentru aproape toate tipurile, inclusiv avizele |
|
||||
| `ff_...:6858` | `scrie_factura_avize` | `Case poDate.Tip = 4` (`:14301,14318`) — doar pentru randurile `custodie=1` ale unei facturi din aviz |
|
||||
| `ff_...:7140` | `scrie_aviz_retur` | **Niciun apel gasit in `COMUN\` din VFP** (`Grep` pe tot `COMUN` si pe tot proiectul — zero rezultate in cod, doar in rapoarte). Posibil cod mort/apelat din alt produs; nu afecteaza verdictele de mai jos, pentru ca `scrie_factura2` singura e suficienta sa dovedeasca A1a. |
|
||||
|
||||
Corectia nu schimba verdictul general al A1a (avizele *ajung* la `contabilizeaza_articol`), dar
|
||||
schimba **prin ce drum** — relevant pentru oricine ar urma sa scrie cod pe baza acestor rapoarte.
|
||||
|
||||
## A1. Ramurile de AVIZ raman atinse si ramura noua le-ar scrie SCD gresit
|
||||
|
||||
**Verdict: CONFIRMAT, dar mai ingust decat sustine raportul.**
|
||||
|
||||
### A1a. Ajunge `contabilizeaza_articol` sa fie apelata pe un AVIZ?
|
||||
|
||||
**DA, confirmat**, prin `scrie_factura2`. Ruta VFP (`ofacturare.vc2:14282-14389`, identic la
|
||||
`:18enable...`, verificata in ambele copii ale formularului):
|
||||
|
||||
```
|
||||
Case poDate.eProforma = 1 -> scrie_proforma
|
||||
Case poDate.Tip = 4 -> scrie_factura_avize && facturare din aviz
|
||||
Case Inlist(poDate.Tip,3,21,25,28,42,47) -> scrie_factura2 && din comenzi / avize din comenzi
|
||||
Otherwise -> scrie_factura2 && tot restul (incl. 29, 22, 24, 27...)
|
||||
```
|
||||
|
||||
In interiorul `scrie_factura2` (`ff_...:6072-6142`), CASE-ul propriu **intercepteaza unele `ntip`
|
||||
inainte** de a ajunge la `contabilizeaza_articol`:
|
||||
|
||||
```
|
||||
WHEN ntip IN (23,25,30,41) THEN transfera_articol(...) -- NU contabilizeaza_articol
|
||||
WHEN ntip IN (42,47) THEN descarca_gestiune(...) direct -- NU contabilizeaza_articol
|
||||
WHEN ntip IN (2,6,52) AND id_rata<>0 THEN contabilizeaza_rata(...)
|
||||
ELSE contabilizeaza_articol(...) -- aici ajung 28, 29, 21, 22, 24, 27, 3, ...
|
||||
```
|
||||
|
||||
Deci `ntip=28` si `ntip=29` **chiar ajung** la `contabilizeaza_articol` (prin `scrie_factura2`, ramura
|
||||
`ELSE`) — asta confirma miezul A1a. Dar `ntip IN (23,25,30,41,42,47)`, desi trec prin `scrie_factura2`
|
||||
(sunt in lista VFP `Inlist(3,21,25,28,42,47)`), **nu ajung niciodata** la `contabilizeaza_articol` —
|
||||
sunt deviate mai devreme, in CASE-ul propriu al lui `scrie_factura2`, spre `transfera_articol`/
|
||||
`descarca_gestiune`. Vezi corectia la sectiunea 3 (tabelul per-`ntip`).
|
||||
|
||||
### A1b. Poate un articol fara politica sa fie adaugat pe un aviz?
|
||||
|
||||
**DA, confirmat structural**, pe doua cai distincte in `adauga_articol_factura` (`ff_...:5052-5203`):
|
||||
|
||||
- `WHEN ntip IN (3,21,28,42,47)` (`:5053-5078`): cauta in `COMENZI_ELEMENTE` dupa
|
||||
`ID_COMANDA + ID_ARTICOL + PRET` — **fara nicio referinta la `V_ID_POL`** in `WHERE`. Un articol
|
||||
fara politica poate gasi potrivire aici daca exista elementul de comanda corespunzator (comanda +
|
||||
articol + pret identice), indiferent de politica. **Fara handler `EXCEPTION`** — la fel ca ramura
|
||||
`ntip=4`, dar aici absenta handler-ului nu conteaza pentru `id_pol`, pentru ca filtrul nu-l
|
||||
foloseste.
|
||||
- `ELSE` (`:5187-5203`, bucket-ul implicit pentru orice `ntip` neacoperit de ramurile speciale —
|
||||
include `29` si restul avizelor "simple"): **nicio interogare legata de politica** — ia direct
|
||||
`V_PRET_TEMP`/`V_ID_VALUTA_TEMP`/`V_PRETURI_CU_TVA_TEMP`/`V_IN_STOC_TEMP` (valorile deja calculate
|
||||
de VFP), plus o singura `SELECT` pe `JTVA_COLOANE` dupa `V_ID_JTVA_COLOANA` (nimic legat de
|
||||
`id_pol`). **Zero obstacol** pentru un articol fara politica pe aceasta ramura.
|
||||
|
||||
Nu am gasit (si nu am cautat exhaustiv, la fel ca raportul original) formularul VFP concret care ar
|
||||
adauga azi un articol ad-hoc fara politica pe un aviz — premisa ramane, ca si in raportul original,
|
||||
mostenita din proiectul #13 (`canal_cont_venit_fara_politica.md`), nu re-derivata aici. Ce am putut
|
||||
confirma exhaustiv e partea Oracle: **daca** VFP trimite `V_ID_POL=NULL` pe aceste `ntip`, Oracle nu
|
||||
pune nicio piedica.
|
||||
|
||||
### A1c. `SCD` in blocul `:7398-7423` — hardcodat sau derivat?
|
||||
|
||||
Citit integral (`ff_...:7398-7423`), confirmat identic cu citatul raportului, **linie cu linie**:
|
||||
|
||||
```
|
||||
ff_...:7400-7406 WHEN ntip<=20 OR ntip IN (nTipFacturaHotel,nTipFacturaRestaurant,
|
||||
nTipNotaPlata,nTipVanzareRetail,48,49,51,52) THEN
|
||||
V_SCD := crs_rand_articol.scd; -- din politica (NOTE_CONTABILE.SCD)
|
||||
ff_...:7413-7417 WHEN ntip IN (28,29) THEN
|
||||
V_SCD := '461'; -- HARDCODAT, aviz catre clienti debitori
|
||||
ff_...:7418-7422 ELSE
|
||||
V_SCD := '418'; -- HARDCODAT, aviz generic
|
||||
```
|
||||
|
||||
`V_SCC` (linia 7425) vine mereu din `crs_rand_articol.scc` (coloana notei de vanzari), **indiferent
|
||||
de ramura** — CASE-ul de mai sus decide doar `V_SCD`. Confirmat: hardcodarea exista exact cum spune
|
||||
raportul, fara offset de linie.
|
||||
|
||||
**Nuanta importanta, omisa de raport**: proiectarea `canal_cont_venit_fara_politica.md:146` (sursa
|
||||
ramurii noi) **flagheaza deja, printr-o paranteza**, ca "ramurile aviz raman `'418'`/`'461'`, deja
|
||||
hardcodate azi la `ff_...:7415,7420`, independent de politica" — deci designul **e constient** de
|
||||
existenta acestor hardcodari si de faptul ca ar trebui pastrate, dar **nu da structura de cod
|
||||
concreta** (niciun `IF`/`CASE` pe `ntip` in tabelul "Continutul ramurii noi", sectiunea 3) care sa
|
||||
implementeze efectiv acea pastrare. Raportul verificat prezinta acest lucru ca pe o **descoperire
|
||||
noua** ("Gol sora... gasit in aceasta cercetare") — de fapt designul deja stia de riscul general, dar
|
||||
nu-l rezolvase in cod. Concluzia practica ramane aceeasi (ramura noua, asa cum e descrisa azi in
|
||||
proiectare, chiar ar hardcoda `SCD='4111'` fara conditionare pe `ntip` daca ar fi implementata
|
||||
literal dupa tabelul din sectiunea 3), dar caracterizarea "gol nou, neconsemnat" e inexacta.
|
||||
|
||||
### Corectie la tabelul per-`ntip` din raport (sectiunea 3)
|
||||
|
||||
Raportul marcheaza `IN (28,29)` **si** "toate celelalte (`21,22,23,24,25,27,30,41,42,47,...`)" ca
|
||||
"Atinsa: DA". Pe baza rutarii reale prin `scrie_factura2` (A1a de mai sus):
|
||||
|
||||
| `ntip` | Ajunge la `contabilizeaza_articol`? | Motiv |
|
||||
|---|---|---|
|
||||
| `28`, `29` | **DA** | `scrie_factura2` -> `ELSE` -> CASE intern `ntip IN(28,29)` -> `SCD='461'` |
|
||||
| `21`, `22`, `24`, `27`, `3` si alte "otherwise" nespeciale | **DA** (probabil) | cad in `ELSE` al lui `scrie_factura2` -> `ELSE` intern -> `SCD='418'` |
|
||||
| `23`, `25`, `30`, `41` | **NU** | interceptate in `scrie_factura2` de `WHEN ntip IN(23,25,30,41) THEN transfera_articol(...)` — nu ajung niciodata la `contabilizeaza_articol` prin acest apelant |
|
||||
| `42`, `47` | **NU** | interceptate de `WHEN ntip IN(42,47) THEN descarca_gestiune(...) direct` — la fel, nu ajung |
|
||||
|
||||
Golul real e deci mai ingust decat "orice aviz": se limiteaza la avizele care cad in ramura `ELSE` a
|
||||
lui `scrie_factura2` (28, 29, si restul avizelor "simple" neexcluse), nu la `23,25,30,41,42,47`, care
|
||||
sunt structural neatinse de `contabilizeaza_articol` prin singurul apelant confirmat. (Nu exclud ca
|
||||
`scrie_aviz_retur`, al carei apelant VFP nu l-am gasit, sa trateze si aceste `ntip` — dar cum nu exista
|
||||
dovada ca e apelata deloc, nu pot confirma nici infirma pentru ea.)
|
||||
|
||||
## A2. Golul `ntip=46` (`nTipNotaPlata`) — `scrie_nota` sarita azi
|
||||
|
||||
**Verdict: CONFIRMAT.**
|
||||
|
||||
- Constanta: `ff_...:84 nTipNotaPlata VANZARI.TIP%TYPE := 46;` — **exact 46**, verificat direct, nu
|
||||
presupus.
|
||||
- Garda: `ff_...:7441 IF pack_facturare.nTip <> pack_facturare.nTipNotaPlata THEN` — inainte de
|
||||
apelul `scrie_nota` (`:7443-7467`) — citat exact, linie identica cu raportul.
|
||||
- Poate un `ntip=46` sa primeasca un articol fara politica? **DA**, prin acelasi bucket `ELSE`
|
||||
(`ff_...:5187-5203`) al `adauga_articol_factura` verificat la A1b — `nTipNotaPlata` nu e in niciuna
|
||||
din ramurile speciale (`3,21,28,42,47`, `4`, `45`, `V_OPT_FACTURARE=3`), deci cade in `ELSE`, care
|
||||
nu cere `id_pol` deloc.
|
||||
- Ajunge la `contabilizeaza_articol`? **DA** — `ntip=46` nu e proforma, nu e `Tip=4`, nu e in
|
||||
`Inlist(3,21,25,28,42,47)` -> `Otherwise` -> `scrie_factura2` -> nu e in `(23,25,30,41)`/`(42,47)`/
|
||||
`(2,6,52)` -> `ELSE` -> `contabilizeaza_articol`.
|
||||
- Ramura noua (per `canal_cont_venit_fara_politica.md` sectiunea 3, pasul 1 "Apeluri, o singura data
|
||||
fiecare") descrie apelul `scrie_nota` **fara** sa mentioneze garda `ntip<>nTipNotaPlata` — deci, asa
|
||||
cum e scrisa azi proiectarea, ar apela necondiționat `scrie_nota` pentru un document `NotaPlata` cu
|
||||
linie `cont_venit`. Gol real, bine argumentat de raport.
|
||||
|
||||
## A3. `ntip=4` exclus prin `NO_DATA_FOUND` neprins la `adauga_articol_factura`
|
||||
|
||||
**Verdict: CONFIRMAT**, cu toate liniile citate verificate exact, fara offset:
|
||||
|
||||
- `ff_...:5080-5103` — ramura `WHEN ntip=4`, `WHERE A.ID_POL = V_ID_POL` (linia 5096 exact), fara
|
||||
`EXCEPTION`. Confirmat caracter cu caracter identic cu citatul raportului.
|
||||
- Semantica SQL `NULL = NULL` -> `UNKNOWN`, niciodata `TRUE`: standard, corect.
|
||||
- **Ordinea de apel confirmata direct pe VFP**: `do_scrie_articole` (`ofacturare.vc2:13967`) cheama
|
||||
`initializeaza_date_factura` (seteaza `ntip`), apoi in bucla `Scan` peste `crsfactura` cheama
|
||||
`adauga_articol_factura` (`:14069-14091`) pentru fiecare rand — **toate acestea ruleaza inainte** de
|
||||
`Do Case` (`:14282`) care alege `scrie_factura_avize`/`scrie_factura2`/etc. Deci un articol cu
|
||||
`V_ID_POL=NULL` pe `ntip=4` cade la `adauga_articol_factura` **inainte** ca `scrie_factura_avize` sa
|
||||
fie chemata — linia nu ajunge niciodata la `contabilizeaza_articol`. Confirma independent
|
||||
concluzia raportului.
|
||||
- **Semantica VFP `Nvl(Alltrim(Str(poArt.id_pol)),[NULL])`** (`ofacturare.vc2:14072`, identic
|
||||
`:18107`): in Visual FoxPro, `Str()` si `Alltrim()` **propaga `.NULL.`** cand primesc `.NULL.` ca
|
||||
argument (comportament standard, documentat al limbajului — orice functie "null-aware" aplicata pe
|
||||
`.NULL.` intoarce `.NULL.`, nu eroare si nu `"0"`). `Nvl(.NULL., [NULL])` inlocuieste explicit cu
|
||||
literalul caracter `NULL` (4 caractere), care ajunge **necotat** in textul SQL generat (concatenare
|
||||
directa, fara ghilimele in jur) — deci Oracle il interpreteaza ca si cuvantul-cheie `NULL`, nu ca
|
||||
string. Rezultat: `V_ID_POL` ajunge `NULL` in Oracle exact cand `poArt.id_pol` e `.NULL.` in VFP.
|
||||
Verificare bazata pe semantica standard VFP (nu am putut rula VFP live — masina partajata, conform
|
||||
interdictiei) — consistenta si cu tiparul deja folosit identic pentru alti parametri opționali pe
|
||||
acelasi apel (`id_gestiune`, `id_valuta_d`, `id_part_rez` etc., toate cu acelasi `Nvl(Alltrim(Str(...`).
|
||||
- **Neverificat** (nici in raportul original, nici aici): cum ajunge efectiv `poArt.id_pol` sa fie
|
||||
`.NULL.` pentru un articol fara politica — ramane premisa mostenita din proiectul #13, nu
|
||||
re-derivata in aceasta runda.
|
||||
|
||||
## A4. Numerele de linie — offset +17 sau identice?
|
||||
|
||||
**Verdict: NICIUNA din cele doua afirmatii e o regula generala — depinde de setul de citate comparat.**
|
||||
|
||||
- **Pentru citatele proprii ale raportului verificat** (re-testate direct aici): `adauga_articol_factura`
|
||||
header `:4989-5015`, ramura `ntip=4` `:5080-5103`, `contabilizeaza_articol` header `:7173`, CASE
|
||||
`:7398-7423`, hardcode `:7413-7422`, garda `:7441`, `nTipNotaPlata:=46` la `:84` — **toate identice**,
|
||||
zero offset. Raportul are dreptate pentru propriile sale citate.
|
||||
- **Dar exista un offset real, doar ca nu e +17 ci +9**, intre citatele mai vechi din
|
||||
`nota_contabila_fara_politica.md:100-104` (`ff_...:6150`, `:6867`, `:7149` pentru cele 3 apeluri
|
||||
`contabilizeaza_articol`) si pozitiile reale confirmate in aceasta runda pentru **aceleasi** apeluri
|
||||
(`6141`, `6858`, `7140`) — diferenta consecventa de **9 linii**, nu 17, si in directia opusa
|
||||
(citatul vechi e mai mare, nu mai mic).
|
||||
- Concluzia corecta: fisierul `PACK_FACTURARE.sql` a fost re-exportat de mai multe ori pe parcursul
|
||||
proiectului (cod adaugat/sters intre versiuni), asa ca **fiecare set de citate vechi are propriul
|
||||
offset fata de exportul curent — nu exista o constanta universala (nici 0, nici +17, nici +9)**.
|
||||
`handoff_13_formular_unificat.md:190` ("+17") si acest raport ("identic, fara offset") sunt ambele
|
||||
adevarate **doar pentru seturile de citate pe care le-au verificat fiecare** — nu se pot generaliza
|
||||
una peste alta. Disciplina corecta, pe care ambele rapoarte au respectat-o, e re-verificarea directa
|
||||
pe fisierul curent inainte de orice citare — nu presupunerea unui offset fix.
|
||||
|
||||
## Text de plan corectat (sectiunea 4 din raportul verificat, rescris)
|
||||
|
||||
```
|
||||
**Ramura `ntip=4` (facturare din avize, handler real `scrie_factura_avize:6692-7058`, nu
|
||||
`scrie_factura_avize_retur` cum apare gresit in rapoartele anterioare) — exclusa structural, nu
|
||||
necesita cod suplimentar.** `adauga_articol_factura`, ramura `WHEN ntip=4` (`:5080-5103`), cauta
|
||||
randul sursa din `VANZARI_DETALII` prin `A.ID_POL = V_ID_POL`, fara handler de exceptie — cu
|
||||
`V_ID_POL=NULL` (exact cazul liniei fara politica), comparatia nu se poate potrivi niciodata, deci
|
||||
apelul cade cu `ORA-01403` la adaugarea articolului, in bucla `do_scrie_articole` (`ofacturare.vc2:
|
||||
13967-14106`), care ruleaza intotdeauna inaintea `Do Case`-ului (`:14282`) ce ar chema
|
||||
`scrie_factura_avize`. Un articol fara politica nu poate fi, structural, adaugat pe un document
|
||||
`ntip=4`; ramura noua `cont_venit` nu are nimic de tratat aici.
|
||||
|
||||
**Gol confirmat, de acoperit inainte de implementare — mai ingust decat s-a crezut initial**:
|
||||
ramurile de aviz care **efectiv** ajung la `contabilizeaza_articol` prin singurul apelant confirmat
|
||||
functional (`scrie_factura2`, apelat pentru `Inlist(3,21,25,28,42,47)` si pentru toate celelalte
|
||||
tipuri prin `Otherwise`) sunt cele care cad in `ELSE`-ul CASE-ului propriu al `scrie_factura2`:
|
||||
`ntip IN (28,29)` -> `SCD='461'`, restul (`21,22,24,27` si alte "otherwise" avize) -> `SCD='418'`
|
||||
(`:7413-7422`). **`ntip IN (23,25,30,41,42,47)` NU ajung la `contabilizeaza_articol`** prin acest
|
||||
apelant — sunt deviate mai devreme, in `scrie_factura2` insasi, spre `transfera_articol`/
|
||||
`descarca_gestiune`, deci golul nu li se aplica (contabilizeaza_articol si ramura ei noua nu se
|
||||
executa niciodata pentru ele). `adauga_articol_factura` nu cere `id_pol` pentru niciunul din aceste
|
||||
`ntip` (foloseste calea "din comenzi", fara filtru pe `id_pol`, sau calea implicita `ELSE`, fara nicio
|
||||
interogare legata de politica) — deci un articol fara politica poate ajunge pe oricare din avizele
|
||||
`28,29,21,22,24,27` cu `cont_venit` populat. Designul deja semnalase acest risc printr-o paranteza
|
||||
(`canal_cont_venit_fara_politica.md:146`, "ramurile aviz raman 418/461"), dar nu-l implementase: ramura
|
||||
noua trebuie sa aleaga `SCD` in functie de `ntip` (pastrand `'461'` pentru `IN(28,29)`, `'418'` pentru
|
||||
restul avizelor atinse), nu sa hardcodeze `'4111'` necondiționat.
|
||||
|
||||
**Gol confirmat separat**: ramura noua trebuie sa pastreze garda `IF ntip <> nTipNotaPlata` (`ntip=46`,
|
||||
`ff_...:7441`) inainte de a apela `scrie_nota` — azi sarita intentionat pentru documentele de tip
|
||||
"nota de plata", iar un articol fara politica poate fi adaugat si pe acest tip de document
|
||||
(`adauga_articol_factura`, bucket `ELSE`, fara cerinta de `id_pol`).
|
||||
|
||||
**Defensiv (opțional)**: se poate adauga o garda explicita `IF cont_venit IS NOT NULL AND ntip = 4
|
||||
THEN RAISE_APPLICATION_ERROR(...)`, dar e demonstrat redundanta — cazul e deja imposibil pe caile VFP
|
||||
cunoscute, blocat cu doua straturi independente inainte de a ajunge la `contabilizeaza_articol`.
|
||||
```
|
||||
|
||||
## Ce nu s-a putut stabili in aceasta runda (mostenit din raportul original, nu re-rezolvat)
|
||||
|
||||
- Cum ajunge `poArt.id_pol` sa fie `.NULL.` in VFP pentru un articol fara politica pe un aviz —
|
||||
premisa proiectului #13, nu re-derivata.
|
||||
- Cine cheama `scrie_aviz_retur` (daca cineva) — cautare exhaustiva in `COMUN\` si in tot proiectul,
|
||||
zero rezultate; posibil cod mort sau apelat din alt produs al suitei ROA.
|
||||
- Comportamentul exact `goExecutor`/`oPrelucrareEroare()` la o exceptie Oracle neprinsa — nu urmarit
|
||||
in aceasta runda (mostenit ca gol si in raportul original).
|
||||
|
||||
## Handoff
|
||||
|
||||
Verificare incheiata, toate cele 4 afirmatii + corectia structurala preliminara sunt in sectiunile de
|
||||
mai sus. Zero cod atins, zero write-back, doar `SELECT`/`Read`/`Grep` pe SQL si VFP text. Context
|
||||
consumat substantial (citire extinsa pe `ff_...sql` si `ofacturare.vc2`), dar verificarea s-a incheiat
|
||||
inainte de pragul de predare.
|
||||
|
||||
---
|
||||
|
||||
## Propagarea `CONT_VENIT` prin cei trei apelanti — verificare
|
||||
|
||||
Intrebare noua de la team-lead: argumentul central al proiectarii (`canal_cont_venit_fara_politica.md`,
|
||||
sectiunea 1, `:63-69`) — "orice coloana noua pe `VANZARI_DETALII_TEMP` ajunge automat in
|
||||
`detalii_articol`, fara sa atingi cei 3 apelanti, pentru ca fac `SELECT * BULK COLLECT` intr-un
|
||||
`TABLE OF ...%ROWTYPE`" — se bazeaza pe lista de nume deja corectata mai sus. Verificat acum pe
|
||||
numele **reale** ale celor 3 apelanti (`scrie_factura2`, `scrie_factura_avize`, `scrie_aviz_retur`),
|
||||
fiecare citit direct, linie cu linie, in aceasta runda.
|
||||
|
||||
### 1-2. `scrie_factura2` (`:6020-6223`)
|
||||
|
||||
```
|
||||
ff_...:6039-6040 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE;
|
||||
tab_detalii tab_detalii_type;
|
||||
ff_...:6063 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP;
|
||||
ff_...:6141 pack_facturare.contabilizeaza_articol(tab_detalii(i));
|
||||
```
|
||||
**`SELECT *` intr-un `TABLE OF VANZARI_DETALII_TEMP%ROWTYPE`, confirmat.** `CONT_VENIT` curge automat.
|
||||
|
||||
### 1-2. `scrie_factura_avize` (`:6692-7058`, handler-ul real `ntip=4`)
|
||||
|
||||
```
|
||||
ff_...:6708-6709 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE;
|
||||
tab_detalii tab_detalii_type;
|
||||
ff_...:6749 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP;
|
||||
ff_...:6858 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- doar pt. custodie=1
|
||||
```
|
||||
**Acelasi tipar: `SELECT *` in `TABLE OF VANZARI_DETALII_TEMP%ROWTYPE`, confirmat.** Intre populare
|
||||
(`:6749`) si apelul catre `contabilizeaza_articol` (`:6858`), randul `tab_detalii(i)` e modificat doar
|
||||
pe doua campuri (`.cantitate`, `.id_rata`, `:6855-6856`) — `CONT_VENIT` ramane neatins, curge automat.
|
||||
Chiar daca numele apelantului real difera de ce credea proiectarea, **concluzia ei ("nu se ating deloc")
|
||||
ramane corecta pentru acest apelant**, doar numele era gresit.
|
||||
|
||||
### 3. `scrie_aviz_retur` (`:7085-7171`)
|
||||
|
||||
```
|
||||
ff_...:7097-7098 TYPE tab_detalii_type IS TABLE OF vanzari_detalii_temp%ROWTYPE;
|
||||
tab_detalii tab_detalii_type;
|
||||
ff_...:7106 SELECT * BULK COLLECT INTO tab_detalii FROM VANZARI_DETALII_TEMP;
|
||||
ff_...:7140 pack_facturare.contabilizeaza_articol(tab_detalii(i)); -- doar pt. custodie=0 (ELSE la :7115)
|
||||
```
|
||||
**Acelasi tipar, confirmat.** Nu conteaza ca nu i-am gasit apelant VFP (ramane nedovedit ca se
|
||||
executa vreodata) — *daca* s-ar executa, `CONT_VENIT` ar curge automat si aici, deci nu schimba
|
||||
suprafata diff-ului indiferent de raspuns.
|
||||
|
||||
### Verdict pe argumentul proiectarii
|
||||
|
||||
**Se salveaza.** Toti cei 3 apelanti reali (nu cei numiti gresit in rapoartele anterioare) folosesc
|
||||
identic `SELECT * BULK COLLECT INTO <TABLE OF VANZARI_DETALII_TEMP%ROWTYPE> FROM VANZARI_DETALII_TEMP`
|
||||
— zero lista explicita de coloane, zero `%ROWTYPE` pe alta tabela, zero constructie camp-cu-camp.
|
||||
Concluzia proiectarii ("adaugi coloana pe `VANZARI_DETALII_TEMP`, `contabilizeaza_articol` o vede
|
||||
automat prin toti apelantii, fara sa atingi `scrie_factura2`/`scrie_factura_avize`/`scrie_aviz_retur`")
|
||||
**e corecta ca mecanism**, chiar daca doua din cele trei nume citate pentru ea erau gresite. Corectia
|
||||
de nume nu schimba nimic la suprafata de regresie a schemei — schimba doar la ce trebuie sa te uiti
|
||||
daca vreodata unul dintre cei trei apelanti isi schimba modul de populare a lui `tab_detalii`.
|
||||
366
docs/cercetare/verif_proforma_alegere_stoc.md
Normal file
366
docs/cercetare/verif_proforma_alegere_stoc.md
Normal file
@@ -0,0 +1,366 @@
|
||||
# Verificare — alegerea stocului pe proforma (runda de verificare)
|
||||
|
||||
Investigatie read-only, 10.08.2026. Raspunde la 4 intrebari punctuale cerute de team-lead, pentru
|
||||
decizia de proiectare S5b (comutare FACTURA <-> PROFORMA cu linii deja adaugate).
|
||||
|
||||
Continua fara sa reia: `s5b_proforma_descarcare_gestiune.md` (mecanismul `gestionabil=0` /
|
||||
`id_gestiune=-1000`) si `s5b_proiectare_proforma_copiere.md` (proiectarea de runda 12, care a
|
||||
descoperit deja ca `scrie_proforma` nu cheama `contabilizeaza_articol` deloc).
|
||||
|
||||
Nicio modificare de cod, niciun `git_sync.ps1`/`txt2vcx.ps1`, niciun commit, nicio scriere Oracle
|
||||
(numai `SELECT`/`Read`/`Grep` pe fisiere de pe disc). Sursa Oracle folosita:
|
||||
`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\08\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql` (citat mai jos
|
||||
`PACK:linie`).
|
||||
|
||||
**Status: COMPLET.**
|
||||
|
||||
---
|
||||
|
||||
## Rezumat executiv
|
||||
|
||||
1. **Da, pe calea principala si pe toate celelalte surse (cu exceptia copierii), stocul NU se
|
||||
alege la adaugarea unei linii pe proforma azi** — pentru ca `gestionabil` a fost deja fortat pe
|
||||
`0` in VFP, in masa, **inainte** ca gridul de linii sa se deschida. Cand ruleaza `Do Case`-ul de
|
||||
la `ofacturare.vc2:13803`, `poArticol.gestionabil` e deja `0`, nu valoarea reala din nomenclator.
|
||||
2. **Marcarea negestionabila are un singur mecanism activ azi, exclusiv in VFP**:
|
||||
`UPDATE (m.lcCursor) SET gestionabil = 0` (`ofacturare.prg:333-336`), rulat in interiorul
|
||||
functiei `factureaza()`, imediat dupa incarcarea cursorului candidat (`crsarticole`) si
|
||||
**inainte** de construirea `crsfactura` (`creeaza_facturacrs`, linia 338) — adica la
|
||||
**deschiderea documentului / incarcarea liniilor candidate**, nu la Termina/salvare. **Exista si
|
||||
un al doilea mecanism, in Oracle**, in `cursor_retur_document` (`PACK:3993-4000`,
|
||||
`CASE WHEN V_PROFORMA=1 THEN 0 ...`), dar acesta e **mort in fluxul curent**: `cursor_retur_document`
|
||||
se cheama dintr-un singur loc (`ofacturare.prg:268`, ramura de copiere), cu
|
||||
`V_PROFORMA = poDate.eProforma` **al documentului nou**, care e **intotdeauna 0** dupa copiere
|
||||
(`nIdTipDoc` nu se propaga de la sursa, `ofacturare_comun.prg:370`) — deci ramura `V_PROFORMA=1`
|
||||
nu se activeaza niciodata azi. **Nu e o contradictie intre cele doua rapoarte anterioare — sunt
|
||||
doua mecanisme reale in cod, dar doar unul (cel VFP) e activ pe traseul de creare a unei
|
||||
proforme; cel Oracle exista doar pentru copiere si acolo nu se declanseaza cu valoarea curenta a
|
||||
parametrului.**
|
||||
3. **`pret_achizitie` depinde de sursa**: pe calea principala (`cursor_preturi`, lista de preturi)
|
||||
NU se completeaza deloc din cursor (coloana nici nu exista in `SELECT`-ul lui `cursor_preturi`)
|
||||
— ramane `0`, prin acelasi tipar de valoare implicita ca la `id_gestiune` (`do_initializeaza_articol`).
|
||||
Pe sursele care incarca dintr-un document existent (`cursor_avize`, `cursor_retur_document` —
|
||||
avize si copiere), `PRET_ACHIZITIE` **e** selectat direct din `VANZARI_DETALII`, deci ajunge real.
|
||||
4. **Daca proforma ar pastra `id_gestiune` real pana la salvare, nimic din contabilizare/stoc nu
|
||||
s-ar strica** — pentru ca blocajul real nu e sentinela `-1000`, ci faptul ca `scrie_proforma`
|
||||
(calea Oracle aleasa la Termina cand `eProforma=1`) **nu cheama niciodata**
|
||||
`contabilizeaza_articol`/`descarca_gestiune` (confirmat deja in raportul-sursa de runda 12).
|
||||
`adauga_articol_factura` (care ar primi `V_ID_GESTIUNE` real) **se cheama oricum**, neconditionat
|
||||
de `eProforma`, pentru orice linie — deci un `id_gestiune` real ar ajunge fara probleme in
|
||||
`VANZARI_DETALII_TEMP`/`VANZARI_DETALII`, fara sa declanseze nimic in plus. **`do_alege_stoc` nu
|
||||
rezerva stoc real** — face doar un `SELECT` (cursoare `cursor_gestiuni_articol*`) si scade local,
|
||||
in memorie, cantitatile deja puse pe documentul curent (`crsfactura`), ca operatorul sa nu aleaga
|
||||
de doua ori acelasi lot; nu scrie nimic in Oracle. Deci lasarea lui `do_alege_stoc` sa ruleze pe
|
||||
proforma nu ar bloca stoc pentru nimeni. Singurul risc real gasit e cel deja semnalat in
|
||||
`s5b_proiectare_proforma_copiere.md` §5 (drumul invers, proforma -> factura in aceeasi sesiune),
|
||||
care ramane valabil indiferent de aceasta decizie.
|
||||
|
||||
---
|
||||
|
||||
## Intrebarea 1 — se alege stocul la adaugarea unui articol pe proforma azi?
|
||||
|
||||
**Raspuns: NU, pe niciuna dintre caile de creare directa a unei proforme** (nu prin copiere).
|
||||
|
||||
### Calea principala — `cursor_preturi`
|
||||
|
||||
`cursor_preturi` (`PACK:2138-2646`) e apelat pentru `tnTip` in `{45, 1, 22, 5, 29, 7, 10, 23}`
|
||||
(`ofacturare.prg:275-282` — lista de preturi, inclusiv `restaurant`), adica cea mai comuna sursa
|
||||
pentru o proforma noua. SELECT-ul lui `cursor_preturi` intoarce `GESTIONABIL` ca
|
||||
`C.IN_STOC AS GESTIONABIL` (`PACK:2192`, `:2297` — valoarea reala din `NOM_ARTICOLE`, fara nicio
|
||||
constienta de proforma; `cursor_preturi` **nu are parametru** `V_PROFORMA`, confirmat prin grep pe
|
||||
tot fisierul: `V_PROFORMA` apare doar la declaratia si corpul lui `cursor_retur_document`,
|
||||
`PACK:408, 3939, 3944, 3952, 3957, 3994`).
|
||||
|
||||
Insa, imediat dupa ce acest cursor e adus in `crsarticole` in VFP, **inainte** ca operatorul sa
|
||||
apuce sa vada gridul de linii:
|
||||
|
||||
```
|
||||
COMUN\programe\ofacturare.prg:330-336
|
||||
* Daca este o proforma, consider toate articolele negestionabile, pentru a nu mai alege din stoc
|
||||
* 12.03.2021
|
||||
IF poDate.eProforma = 1
|
||||
UPDATE (m.lcCursor) SET gestionabil = 0
|
||||
GO TOP IN (m.lcCursor)
|
||||
ENDIF
|
||||
```
|
||||
|
||||
`m.lcCursor` = `crsarticole` (setat la `:310`). Acest bloc ruleaza **dupa** `Do Case`-ul de
|
||||
incarcare (`:266-308`) si **inainte** de `creeaza_facturacrs([crsfactura])` (`:338`) — adica la
|
||||
compunerea listei de articole candidate, nu la salvare. Cand operatorul adauga o linie mai tarziu,
|
||||
`Do Case`-ul care alege dialogul (`ofacturare.vc2:13803-13809`) citeste `poArticol.gestionabil`,
|
||||
care e deja `0` pentru **toate** liniile candidate (setat in masa mai sus), deci intra pe ramura
|
||||
`frm_articol_factura`, **niciodata** pe `Thisform.do_alege_stoc`.
|
||||
|
||||
### Celelalte cursoare (fara copiere)
|
||||
|
||||
Verificat direct in `PACK_FACTURARE`, niciunul din urmatoarele cursoare, folosite pentru celelalte
|
||||
surse ale unei proforme noi, nu are parametru `V_PROFORMA` si nici nu calculeaza `GESTIONABIL` in
|
||||
functie de proforma — toate intorc valoarea reala din nomenclator/contract:
|
||||
|
||||
| Cursor | Apelat pentru (`tnTip`) | `GESTIONABIL` in SELECT | Linie |
|
||||
|---|---|---|---|
|
||||
| `cursor_preturi` | 45,1,22,5,29,7,10,23 | `C.IN_STOC` | `PACK:2192,2297` |
|
||||
| `cursor_contract` | 2,26,6,52 | `A.GESTIONABIL` (coloana reala) | `PACK:2411,2487,2572` |
|
||||
| `cursor_comanda` | 3,21,25,28,42,47 | `E.IN_STOC` | `PACK:2789` |
|
||||
| `cursor_lucrare` | 27 | `C.IN_STOC` | `PACK:3029` |
|
||||
| `cursor_articole_k` | 48,49 | `C.IN_STOC` | `PACK:3117` |
|
||||
| `cursor_avize` | 4 | `C.IN_STOC` | `PACK:3634` |
|
||||
| `cursor_aviz_nir` | 30 | `0` (hardcodat) | `PACK:3745` |
|
||||
| `cursor_gestiune` | 41 | `A.GESTIONABIL` | `PACK:4104` |
|
||||
| `cursor_retur` | 8,9,24 | (nu are coloana `GESTIONABIL` separata — vezi nota) | `PACK:3934-3948` |
|
||||
|
||||
Pentru **toate** aceste surse, aceeasi bucla `ofacturare.prg:330-336` e singurul loc care forteaza
|
||||
`gestionabil=0` cand `poDate.eProforma=1` — mecanismul e identic indiferent de sursa, pentru ca
|
||||
`UPDATE (m.lcCursor) SET gestionabil = 0` ruleaza pe `crsarticole` dupa orice ramura a `Do Case`-ului
|
||||
de la `:266-308`, nu doar pe ramura lista-de-preturi.
|
||||
|
||||
### Ramura de copiere — singura cu parametru `V_PROFORMA` in Oracle
|
||||
|
||||
`Case m.llCopiere` (`ofacturare.prg:267-268`) e **singura** ramura care apeleaza
|
||||
`cursor_retur_document`, singurul cursor cu parametru `V_PROFORMA`. Insa la copiere, documentul nou
|
||||
pleaca intotdeauna cu `eProforma=0` (`nIdTipDoc` explicit necopiat, `ofacturare_comun.prg:370` —
|
||||
deja stabilit in `s5b_proiectare_proforma_copiere.md` §2.3), deci `V_PROFORMA` trimis e `0`, iar
|
||||
ramura `GESTIONABIL = B.IN_STOC` (valoarea reala) se activeaza, nu `WHEN V_PROFORMA=1 THEN 0`. Asta
|
||||
e comportamentul **corect si dorit** la copiere (liniile redevin gestionabile) — dar confirma ca
|
||||
`V_PROFORMA=1` nu apare niciodata cu valoarea `1` in vreun apel real azi (vezi Intrebarea 2).
|
||||
|
||||
**Concluzie Intrebarea 1**: pe orice cale de creare directa a unei proforme (nu copiere), la
|
||||
momentul `Do Case`-ului de adaugare a liniei (`ofacturare.vc2:13803`), `poArticol.gestionabil` e
|
||||
deja `0` — fortat de VFP la incarcare, nu valoarea reala din nomenclator. `do_alege_stoc` nu ruleaza
|
||||
niciodata pentru o linie noua adaugata sub `eProforma=1`, indiferent de sursa.
|
||||
|
||||
---
|
||||
|
||||
## Intrebarea 2 — unde exact se face marcarea negestionabila si CAND?
|
||||
|
||||
**Doua locuri in cod, dar un singur loc activ azi:**
|
||||
|
||||
### 2.1 VFP — activ, la incarcarea documentului (nu la salvare)
|
||||
|
||||
`COMUN\programe\ofacturare.prg:333-336`, in interiorul procedurii `factureaza()`. Secventa completa
|
||||
in `factureaza()`:
|
||||
|
||||
1. `:266-308` — `Do Case` pe `tnTip`/`llCopiere`, alege cursorul Oracle si il aduce in `crsarticole`.
|
||||
2. `:311` — `goExecutor.oExecute(lcSqlCursor, lcCursor)` — executa efectiv apelul Oracle.
|
||||
3. `:324-336` — daca sunt randuri, **si daca `poDate.eProforma = 1`**: `UPDATE crsarticole SET gestionabil = 0`.
|
||||
4. `:338` — abia acum `creeaza_facturacrs([crsfactura])` construieste cursorul gol de linii ale
|
||||
documentului; liniile candidate (`crsarticole`, deja marcate) raman disponibile pentru ca
|
||||
operatorul sa aleaga din ele.
|
||||
|
||||
Acest pas ruleaza **la deschiderea ecranului de adaugare articole**, mult inainte de `Termina`
|
||||
(`do_scrie_factura`, apelat doar la click pe butonul de finalizare — vezi 2.3 mai jos). Nu exista
|
||||
niciun `UPDATE`/`REPLACE gestionabil With 0` la salvare in `do_scrie_factura` sau `do_scrie_articole`
|
||||
— am cautat explicit `gestionabil` in ambele metode (`ofacturare.vc2:14197-14380`,`:13967-14150`) si
|
||||
singura referinta la `gestionabil` in acea zona e citirea lui la `Do Case`-ul din `:13803` (decizia
|
||||
de dialog), nu o scriere.
|
||||
|
||||
### 2.2 Oracle — exista in cod, dar mort in fluxul curent
|
||||
|
||||
`cursor_retur_document` (`PACK:3949-4064`), branch la `PACK:3993-4000`:
|
||||
```sql
|
||||
(case
|
||||
when V_PROFORMA = 1 then 0
|
||||
when V_COPIERE = 1 then B.IN_STOC
|
||||
else A.GESTIONABIL
|
||||
end) AS GESTIONABIL,
|
||||
```
|
||||
cu comentariu explicit in cod: `-- V_PROFORMA: Daca este proforma (1), fac articolele negestionabile
|
||||
sa pot alege orice cantitate` (`PACK:3957`). Acest cod **exista si e corect scris**, dar
|
||||
`cursor_retur_document` are un singur punct de apel in tot codul VFP (confirmat prin cautare in
|
||||
`ofacturare.prg`, singura potrivire e la `:268`; potrivirile suplimentare gasite in
|
||||
`COMUN\.svn\pristine\*` sunt copii istorice ale **aceleiasi** linii, nu apeluri suplimentare), si
|
||||
acolo `V_PROFORMA` trimis e `poDate.eProforma` **al documentului nou** din copiere, care e
|
||||
intotdeauna `0` (Intrebarea 1). **Deci acest branch Oracle nu se activeaza niciodata cu valoarea 1
|
||||
in fluxul curent** — e cod mort din perspectiva efectului observabil, desi sintactic corect si
|
||||
prezent.
|
||||
|
||||
### 2.3 Clarificare pe "inainte de compunerea documentului" vs. "la salvare"
|
||||
|
||||
Formularea raportului de runda 9 ("VFP marcheaza toate liniile proformei negestionabile inainte de
|
||||
compunerea documentului") e **corecta** — "compunerea documentului" inseamna acolo constructia
|
||||
listei de articole candidate/`crsfactura` (pasul 4 de mai sus), care se intampla la
|
||||
**deschiderea** ecranului de facturare, nu la apasarea `Termina`. Nu exista o contradictie reala cu
|
||||
mentiunea `V_PROFORMA` din `cursor_retur_document` din brief — acel `CASE` Oracle e un mecanism
|
||||
separat, pentru un cursor diferit (candidati la copiere), care azi nu se declanseaza niciodata cu
|
||||
`V_PROFORMA=1`. Ambele afirmatii din surse sunt adevarate simultan, pentru ca descriu lucruri
|
||||
diferite.
|
||||
|
||||
**Concluzie Intrebarea 2**: singurul mecanism activ e `UPDATE` de masa in VFP
|
||||
(`ofacturare.prg:333-336`), care ruleaza in `factureaza()`, la incarcarea/compunerea listei de
|
||||
articole candidate — **cu mult inainte** de `Termina`/`do_scrie_factura`. Mecanismul Oracle din
|
||||
`cursor_retur_document` exista in cod dar nu se activeaza cu valoarea curenta a parametrilor.
|
||||
|
||||
---
|
||||
|
||||
## Intrebarea 3 — se completeaza `pret_achizitie` pe liniile de proforma?
|
||||
|
||||
**Depinde strict de sursa cursorului, la fel ca la `gestionabil` — dar cu rezultat opus pentru
|
||||
calea principala.**
|
||||
|
||||
### Ce cursoare selecteaza `PRET_ACHIZITIE`
|
||||
|
||||
Cautare directa in `PACK_FACTURARE` (`grep PRET_ACHIZITIE`): coloana **nu apare deloc** in
|
||||
`cursor_preturi` (`PACK:2138-2646`), `cursor_contract` (`:2646-2952`), `cursor_comanda`
|
||||
(`:2952-3173`), `cursor_lucrare` (`:3173-3595`), `cursor_articole_k` (`:3595-3703`),
|
||||
`cursor_gestiune` (`:4158-4349`). Apare doar in:
|
||||
- `cursor_avize` (in jurul liniilor `PACK:3754-3864` — sursa `VANZARI_DETALII`/`RUL`, articol
|
||||
provenit dintr-un aviz existent);
|
||||
- `cursor_retur_document` (`PACK:4026`, `A.PRET_ACHIZITIE` direct din `VANZARI_DETALII`, folosit la
|
||||
copiere).
|
||||
|
||||
### Ce se intampla in VFP cand lipseste
|
||||
|
||||
`crsfactura` (structura din `creeaza_facturacrs`, `ofacturare_comun.prg:1777`) are un camp
|
||||
`pret_achizitie` in schema — dar o linie noua nu se construieste prin copiere in masa din
|
||||
`crsarticole`, ci prin `Scatter`/`Gather` pe obiectul `poArticol`, populat cand utilizatorul alege un
|
||||
articol din grid. `do_initializeaza_articol` (`ofacturare.vc2:13715-13719`), apelat pentru orice
|
||||
linie care trece prin `frm_articol_factura` (ramura negestionabila, deci si orice linie de
|
||||
proforma):
|
||||
```
|
||||
If Type('toArticol.pret_achizitie')="U"
|
||||
AddProperty(toArticol,'pret_achizitie',0)
|
||||
Endif
|
||||
If Isnull(toArticol.pret_achizitie)
|
||||
toArticol.pret_achizitie = 0
|
||||
Endif
|
||||
```
|
||||
Deci: **pe calea principala (lista de preturi), `pret_achizitie` ramane `0`** pe liniile de proforma
|
||||
— campul nici nu exista in `crsarticole` sursa (cursorul Oracle nu-l selecteaza), asa ca proprietatea
|
||||
lipseste pe `poArticol` si `do_initializeaza_articol` o creeaza cu valoare implicita `0`, exact
|
||||
acelasi tipar defensiv folosit pentru `id_gestiune=-1000` (aceeasi metoda, linii apropiate,
|
||||
`:13618-13623` vs `:13715-13719`).
|
||||
|
||||
**Pe sursele care carata un document existent** (`cursor_avize`, si `cursor_retur_document` la
|
||||
copiere), `pret_achizitie` **e** populat real din `VANZARI_DETALII.PRET_ACHIZITIE` a documentului
|
||||
sursa, deci proprietatea exista pe `poArticol` si `do_initializeaza_articol` nu o suprascrie.
|
||||
|
||||
### Unde ajunge
|
||||
|
||||
Indiferent de valoare (`0` sau reala), `poArt.pret_achizitie` merge neschimbat catre Oracle ca
|
||||
parametru pozitional in apelul catre `adauga_articol_factura`:
|
||||
```
|
||||
ofacturare.vc2:14073
|
||||
... + Alltrim(Str(poArt.pret_achizitie,18,gnPPret)) + [,] + ...
|
||||
```
|
||||
primit ca `V_PRET_ACHIZITIE_TEMP` (`PACK:4995`), copiat direct `V_PRET_ACHIZITIE := V_PRET_ACHIZITIE_TEMP`
|
||||
(`PACK:5031`, fara sentinela, spre deosebire de `id_gestiune`) si scris ca atare in
|
||||
`VANZARI_DETALII_TEMP.PRET_ACHIZITIE` (`PACK:5229/5259`), apoi in `VANZARI_DETALII` la
|
||||
`scrie_in_vanzari`.
|
||||
|
||||
**Concluzie Intrebarea 3**: pe calea principala (lista de preturi), `pret_achizitie` ramane `0` pe
|
||||
liniile de proforma — nu vine din `do_alege_stoc` (care nu ruleaza) si nici din cursorul Oracle (care
|
||||
nu-l selecteaza pe aceasta ramura). Pe sursele avize/copiere, valoarea reala din documentul sursa se
|
||||
pastreaza.
|
||||
|
||||
---
|
||||
|
||||
## Intrebarea 4 — ce s-ar strica daca proforma ar pastra gestiunea reala pana la salvare?
|
||||
|
||||
### 4a. `adauga_articol_factura` se cheama si pentru liniile de proforma?
|
||||
|
||||
**Da, neconditionat.** `do_scrie_factura` (`ofacturare.vc2:14197+`) cheama
|
||||
`Thisform.do_scrie_articole()` la linia **14264**, **inainte** de `Do Case poDate.eProforma = 1`
|
||||
(care alege intre `scrie_proforma` si `scrie_factura2`, la `:14282`):
|
||||
```
|
||||
ofacturare.vc2:14264
|
||||
llReturn = Thisform.do_scrie_articole() && se deschide tranzactie manuala
|
||||
...
|
||||
ofacturare.vc2:14282
|
||||
Do Case
|
||||
Case poDate.eProforma = 1
|
||||
* scrie_proforma are aceiasi parametri ca scrie_factura2
|
||||
* salveaza doar in vanzari, nu si in contabilitate
|
||||
lcSql = [{call pack_facturare.scrie_proforma(...)}]
|
||||
```
|
||||
`do_scrie_articole` parcurge liniile din `crsfactura` si cheama `adauga_articol_factura` pentru
|
||||
fiecare (`ofacturare.vc2:14069-14073`, deja citat in raportul-sursa) — **acelasi cod, indiferent de
|
||||
`eProforma`**. Daca `poArt.id_gestiune` ar fi real (nu `-1000`), `adauga_articol_factura` l-ar
|
||||
traduce direct in `V_ID_GESTIUNE2 := V_ID_GESTIUNE` (`PACK:5032-5034`, gate-ul `<> -1000`) si l-ar
|
||||
scrie ca atare in `VANZARI_DETALII_TEMP.ID_GESTIUNE` (`PACK:5237,5267`), apoi in
|
||||
`VANZARI_DETALII.ID_GESTIUNE` la `scrie_in_vanzari` (`INSERT INTO VANZARI_DETALII SELECT FROM
|
||||
VANZARI_DETALII_TEMP`, fara nicio conditie suplimentara pe `id_gestiune`).
|
||||
|
||||
### 4b. Are efect ca linia sa ramana cu `ID_GESTIUNE` completat, cata vreme `scrie_proforma` nu cheama `contabilizeaza_articol`?
|
||||
|
||||
**Niciunul, pe partea de contabilizare/descarcare.** Confirmat deja (runda 12,
|
||||
`s5b_proiectare_proforma_copiere.md`, sectiunea "Descoperire centrala"): `scrie_proforma`
|
||||
(`PACK:5637-5671`) cheama **doar** `scrie_in_vanzari` — insereaza antetul si liniile ca atare, apoi
|
||||
seteaza `VANZARI.EPROFORMA=1`. **Nu cheama** `contabilizeaza_articol` in niciun caz. Sentinela
|
||||
`-1000` conteaza doar **in interiorul** lui `contabilizeaza_articol` (`PACK:7472-7476`), care nu
|
||||
ruleaza deloc pentru `scrie_proforma`. Deci `ID_GESTIUNE` real pe o linie de proforma nu ar declansa
|
||||
`descarca_gestiune`, nu ar genera `NOTE_CONTABILE`, nu ar atinge `RUL`/`STOC` — pentru ca intreaga
|
||||
functie care ar face asta nu se executa pe traseul `scrie_proforma`.
|
||||
|
||||
### 4c. Rapoarte/view-uri care citesc `ID_GESTIUNE` pe documente `eProforma=1` si ar arata altceva?
|
||||
|
||||
Cautare `eproforma` (case-insensitive) in tot `PACK_FACTURARE`: doar 3 potriviri, toate in
|
||||
`scrie_proforma`/comentarii (`PACK:1408, 5666, 5668`) — nicio alta procedura/vedere din pachet nu
|
||||
filtreaza sau calculeaza dupa `EPROFORMA`.
|
||||
|
||||
Pe partea VFP, `crsDetaliiListare` (folosit la **relistarea** unui document deja emis, inclusiv
|
||||
proforma — `oproceduri_facturare.prg:1111-1126`, sursa `fact_vfacturi2`) **include** coloana
|
||||
`id_gestiune` in schema si in `SELECT`, indiferent de `eproforma` (nu exista filtru pe
|
||||
`eproforma` in acest `SELECT`). Deci daca `ID_GESTIUNE` ar fi real pe o proforma, acest cursor l-ar
|
||||
citi ca atare la relistare — azi citeste `NULL`. **Nu am putut confirma daca vreun raport `.frx`
|
||||
tiparit efectiv afiseaza `id_gestiune`/`nume_gestiune` pe documentul proforma insusi**
|
||||
(rapoartele `PROFORMA`/`PROFORMA_VAL` nu au versiune text `.fr2` generata in `Rapoarte\`, deci nu
|
||||
s-au putut grep-ui direct) — de regula gestiunea e un camp intern, nu unul tiparit pe un document
|
||||
comercial, dar afirmatia ramane neconfirmata pe cod, nu doar presupusa corecta. Vezi "Ce nu s-a
|
||||
putut stabili".
|
||||
|
||||
### 4d. `do_alege_stoc` rezerva stoc sau doar alege?
|
||||
|
||||
**Doar alege — nu scrie nimic in Oracle.** Corpul complet al `do_alege_stoc`
|
||||
(`ofacturare.vc2:13200-13400+`) face exclusiv: (1) un `SELECT` prin
|
||||
`cursor_gestiuni_articol`/`cursor_gestiuni_articol_retur`/`cursor_gestiuni_articol_stoc0` (toate
|
||||
proceduri read-only, populeaza un cursor local `crsgestarticoltemp`), (2) o scadere **locala, in
|
||||
memorie**, a cantitatilor deja adaugate pe **acelasi document, in aceeasi sesiune**
|
||||
(`ofacturare.vc2:13273-13311`: `SELECT ... FROM crsfactura ... INTO CURSOR crsFacturaArticoleTemp`,
|
||||
apoi `a.cantitate - Nvl(b.cantitate,0)` la afisare), ca sa nu i se ofere operatorului sa aleaga de
|
||||
doua ori acelasi lot pe acelasi document. **Nu exista niciun `INSERT`/`UPDATE` catre Oracle in
|
||||
aceasta metoda** — `goExecutor.oExecute` apare o singura data, pentru `SELECT`-ul de la pasul (1).
|
||||
Blocarea/decrementarea reala a stocului se intampla abia la `descarca_gestiune`
|
||||
(`PACK:7648-7797`), apelata doar din `contabilizeaza_articol`, care (per 4b) nu ruleaza niciodata
|
||||
pentru `scrie_proforma`. **Deci chiar daca `do_alege_stoc` ar rula pe liniile unei proforme, nu ar
|
||||
"tine" stoc blocat pentru nimeni** — nu exista mecanism de rezervare reala in aplicatie pe acest
|
||||
traseu, doar o verificare locala de neduplicare in sesiunea curenta.
|
||||
|
||||
**Concluzie Intrebarea 4**: nimic din contabilizare, descarcare de gestiune sau stoc real nu s-ar
|
||||
strica daca proforma ar pastra `id_gestiune` real pana la salvare — blocajul functional e la nivelul
|
||||
routing-ului `scrie_proforma` (care nu cheama `contabilizeaza_articol`), nu la nivelul sentinelei
|
||||
`-1000`. Singurul loc unde s-ar vedea o diferenta reala e la **relistarea** documentului
|
||||
(`crsDetaliiListare`/`fact_vfacturi2`), unde `id_gestiune` ar aparea completat in loc de `NULL` —
|
||||
efect cosmetic/informational, nu unul care sa afecteze stocul sau contabilitatea, cu rezerva ca nu
|
||||
s-a confirmat daca vreun `.frx` chiar il tipareste. Riscul real ramas e cel deja identificat in
|
||||
`s5b_proiectare_proforma_copiere.md` §5 (drumul invers, proforma comutata inapoi in factura in
|
||||
aceeasi sesiune, inainte de Termina) — acela nu se schimba, indiferent de aceasta decizie, pentru ca
|
||||
depinde de routing-ul de la Termina, nu de momentul in care se alege gestiunea.
|
||||
|
||||
---
|
||||
|
||||
## Ce nu s-a putut stabili
|
||||
|
||||
1. **Daca vreun raport `.frx` tiparit (PROFORMA/PROFORMA_VAL) afiseaza efectiv `id_gestiune` sau
|
||||
`nume_gestiune` pe documentul insusi** — rapoartele nu au versiune text `.fr2` generata in
|
||||
`Rapoarte\`, asa ca nu s-a putut grep pe continutul lor; ar necesita fie generarea textului cu
|
||||
`git_sync.ps1`/`foxbin2prg` (in afara mandatului read-only), fie deschidere in IDE VFP.
|
||||
2. **Nu s-a rulat nimic pe date vii** — toata analiza e trasare de cod static (VFP text + PL/SQL
|
||||
text), conform mandatului read-only.
|
||||
3. **Cautarea "eproforma" in Oracle** a acoperit doar `PACK_FACTURARE` — nu s-au verificat alte
|
||||
pachete/view-uri/rapoarte Oracle care ar putea referi `VANZARI.EPROFORMA` impreuna cu
|
||||
`ID_GESTIUNE` (de exemplu rapoarte de gestiune/stoc din alte module ROA). Zero rezultate in
|
||||
`PACK_FACTURARE` nu e dovada de completitudine pentru intreaga schema.
|
||||
4. **`cursor_retur`** (`PACK:3934-3948`, folosit pentru `tnTip` 8,9,24 — retururi) nu a fost citit
|
||||
integral pentru coloana `GESTIONABIL` (tabelul de la Intrebarea 1 il listeaza fara valoare
|
||||
confirmata) — nu are parametru `V_PROFORMA` (confirmat prin grep), deci concluzia generala
|
||||
(marcarea vine doar din VFP) ramane valabila, dar valoarea exacta intoarsa de coloana nu a fost
|
||||
verificata linie-cu-linie.
|
||||
|
||||
---
|
||||
|
||||
## Handoff
|
||||
|
||||
Cercetare incheiata intr-o singura sesiune, fara sa fie nevoie de predare de context. Toate cele 4
|
||||
intrebari din brief au raspuns cu dovada `fisier:linie`, plus rezumatul executiv de mai sus. Niciun
|
||||
fisier de cod atins, niciun `git_sync.ps1`/`txt2vcx.ps1` rulat, niciun commit, nicio scriere pe
|
||||
Oracle.
|
||||
262
docs/cercetare/zi_curs_validare.md
Normal file
262
docs/cercetare/zi_curs_validare.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Cercetare: validarea zi_curs la ofacturare.vc2:8076 si impactul asupra S4d
|
||||
|
||||
## Verdict (5-10 randuri)
|
||||
|
||||
Ascunderea selectorului de zi curs NU va lasa un document fara curs si NU va cadea la salvare,
|
||||
CU CONDITIA sa se respecte precedentul deja existent in cod: `poDate.zi_curs` primeste un implicit
|
||||
necondiionat (data documentului) chiar in `oDateFactura.Init`/`Reset`
|
||||
(`COMUN\programe\ofacturare_comun.prg:247` si `:496`), INAINTE ca formularul sa decida ce ascunde.
|
||||
Nicaieri codul nu goleste `poDate.zi_curs` cand controlul e ascuns/eliminat. Riscul real de eroare
|
||||
Oracle (-20005, "Nu este setat cursul...") vine NU din camp gol, ci din faptul ca verificarea
|
||||
`pack_facturare.verifica_cursuri_valute` (in `cursor_preturi`) ruleaza NECONDITIONAT de `in_valuta`
|
||||
al documentului curent si exclude doar moneda nationala - deci un `zi_curs` implicit (azi) care nu
|
||||
are curs setat in tabela CURS pentru o valuta folosita in listele de preturi ale utilizatorului
|
||||
poate pica oricum, INDIFERENT daca selectorul e vizibil sau nu. Linia :8076 NU apartine formularului
|
||||
de factura, ci unui formular separat, restrans, pentru AVIZ PE LUCRARE / AVIZ PE NIR
|
||||
(`frm_date_aviz_lucrare`, tipuri 27 si 30) - fara control de valuta pe el - si valideaza `zi_curs`
|
||||
strict pentru ca tipul 27 are nevoie de curs pentru articolele din comanda (posibil in valuta),
|
||||
INDEPENDENT de `poDate.in_valuta`. Precedentul cerut la punctul 6 exista deja, dar in alt formular:
|
||||
`frm_date_factura`, tipurile 8/9 (retur), unde `clb_zi_curs` e eliminat NECONDITIONAT si documentul
|
||||
se salveaza corect - motivul principal e ca SQL-ul pentru retur (`cursor_retur`) nici nu foloseste
|
||||
`poDate.zi_curs`.
|
||||
|
||||
## 1. Ce valideaza linia :8076
|
||||
|
||||
Index de simboluri: `frm_date_aviz_lucrare.inainte_de_do_termin` = `ofacturare.vc2:8054-8119`
|
||||
(fisier real: `COMUN\clase\ofacturare.vc2`).
|
||||
|
||||
Blocul complet (validare secventiala pe formular, fiecare `Case` opreste salvarea la primul fail):
|
||||
|
||||
```
|
||||
COMUN\clase\ofacturare.vc2:8063-8079
|
||||
Do Case
|
||||
Case Empty(poDate.dataireg)
|
||||
amessagebox("Nu ati completat data inregistrarii!",48,"Atentie")
|
||||
...
|
||||
Case Empty(poDate.dataact)
|
||||
amessagebox("Nu ati completat data documentului!",48,"Atentie")
|
||||
...
|
||||
Case Empty(Nvl(poDate.id_fdoc,0))
|
||||
amessagebox("Nu ati ales felul documentului!",48,"Atentie")
|
||||
...
|
||||
Case Empty(Nvl(poDate.zi_curs,{}))
|
||||
amessagebox("Nu ati completat ziua cursului valutar!",48,"Atentie")
|
||||
This.clb_zi_curs.SetFocus()
|
||||
plReturn = .F.
|
||||
Case Empty(poDate.nract)
|
||||
...
|
||||
```
|
||||
|
||||
Valideaza STRICT prezenta unei date in `poDate.zi_curs` (`Empty(Nvl(...,{}))`), nu existenta unui
|
||||
curs in baza pentru acea data - acel test se face abia in Oracle, la momentul in care se cere
|
||||
cursorul de articole (vezi punctul 4).
|
||||
|
||||
## 2. Cand ruleaza si pe ce tipuri de document
|
||||
|
||||
`inainte_de_do_termin` e apelat la evenimentul butonului "Termina" al formularului (`BUT_TERMIN1`,
|
||||
vezi lista de obiecte a clasei, `ofacturare.vc2:7674-7679`) - deci la incercarea de a incheia
|
||||
completarea datelor de antet, inainte de a trece la ecranul de articole.
|
||||
|
||||
`frm_date_aviz_lucrare` NU e formularul de factura. E instantiat DOAR pentru doua tipuri de
|
||||
document, ambele AVIZ (nu FACTURA):
|
||||
|
||||
```
|
||||
COMUN\programe\ofacturare.prg:187-226 (identic in factureaza2, :697-699)
|
||||
Do Case
|
||||
Case tnTip = 27
|
||||
poDate.nIdTipDoc = 6 && AVIZ
|
||||
Case tnTip = 30
|
||||
poDate.nIdTipDoc = 6 && AVIZ
|
||||
Case tnTip < 21 Or Inlist(tnTip, 45, 48, 49, 51, 52)
|
||||
poDate.nIdTipDoc = 5 && FACTURA
|
||||
...
|
||||
Do Case
|
||||
Case tnTip = 27
|
||||
lcObiect = [frm_date_aviz_lucrare]
|
||||
Case tnTip = 30
|
||||
lcObiect = [frm_date_aviz_lucrare]
|
||||
Case tnTip < 21 Or Inlist(tnTip, 45, 48, 49, 51, 52)
|
||||
lcObiect = [frm_date_factura]
|
||||
Otherwise
|
||||
lcObiect = [frm_date_aviz]
|
||||
Endcase
|
||||
```
|
||||
|
||||
Titlul formularului confirma: `Lb_titlu_alb_b121.Caption = "AVIZ PE BAZ DE LUCRARE"`
|
||||
(`ofacturare.vc2:7670`), schimbat in `Init` la "AVIZ PE BAZ DE NIR" cand `nid_tip = 30`
|
||||
(`ofacturare.vc2:8154-8161`).
|
||||
|
||||
Nu exista o garda mai sus in lant care sa dezactiveze validarea :8076 pe vreun tip - ruleaza
|
||||
identic pentru tip 27 si tip 30, necondiionat de `poDate.in_valuta`. IMPORTANT: aceasta clasa
|
||||
NU are deloc control de valuta pe formular - lista de obiecte a clasei (`ofacturare.vc2:7623-7637`)
|
||||
nu contine niciun `ct_clb_valuta`. Deci validarea de aici nu e legata de "documentul e in valuta",
|
||||
ci de nevoia formularului AVIZ-LUCRARE de a avea o zi de curs pentru articolele comenzii care pot
|
||||
fi preturite in valuta (vezi punctul 4, `cursor_lucrare`).
|
||||
|
||||
Concluzie: linia :8076 NU intra deloc in fluxul de facturare (FACTURA) vizat de decizia 15 - e un
|
||||
formular separat, pentru un subset ingust de avize (27 = aviz pe lucrare, 30 = aviz pe NIR).
|
||||
|
||||
## 3. Ce se intampla daca zi_curs e gol la :8076
|
||||
|
||||
Mesaj de eroare blocant (nu doar avertisment): `amessagebox("Nu ati completat ziua cursului
|
||||
valutar!",48,"Atentie")`, apoi `This.clb_zi_curs.SetFocus()` si `plReturn = .F.` - `Case`-ul
|
||||
opreste executia `Do Case` (Otherwise nu se mai atinge), iar `inainte_de_do_termin` returneaza
|
||||
`.F.`, ceea ce (conform conventiei din restul clasei) blocheaza inchiderea formularului / trecerea
|
||||
la pasul urmator.
|
||||
|
||||
## 4. Cine mai citeste poDate.zi_curs
|
||||
|
||||
Cautare `zi_curs` in `.vc2`/`.prg`/`.sc2` si in sursele Oracle
|
||||
(`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql`):
|
||||
|
||||
**a) Validari de formular (camp obligatoriu):**
|
||||
- `frm_date_aviz_lucrare.inainte_de_do_termin` :8076 - necondiionat (vezi punctele 1-2).
|
||||
- `frm_date_factura.inainte_de_do_termin`, `ofacturare.vc2:9484`:
|
||||
```
|
||||
Case poDate.in_valuta = 1 And Empty(Nvl(poDate.zi_curs,{})) And Type('thisform.clb_zi_curs.visible')<>'U'
|
||||
```
|
||||
Aici validarea E DEJA dublu conditionata: pe `in_valuta = 1` SI pe existenta controlului
|
||||
(`Type(...)<>'U'` - devine 'U' daca controlul a fost eliminat cu `RemoveObject`). Deci pe factura
|
||||
in lei, sau pe orice tip unde controlul a fost eliminat, validarea nu ruleaza deloc. Comentariul
|
||||
`*!* modificare v 2.0.56` de langa arata ca exact acest lucru a fost REZOLVAT anterior pentru
|
||||
formularul de factura.
|
||||
|
||||
**b) Populare implicita / sincronizare (fara conditie de in_valuta):**
|
||||
- `oDateFactura.Init`, `COMUN\programe\ofacturare_comun.prg:247`: `.zi_curs = ldData` -
|
||||
necondiionat, seteaza mereu data documentului curent (ldData = azi, ajustat la luna/anul
|
||||
curent de facturare) INAINTE de blocul care seteaza `.in_valuta` (linia 248-250).
|
||||
- `oDateFactura.Reset`, `ofacturare_comun.prg:496`: `.zi_curs = .Data` - la fel, necondiionat.
|
||||
- `frm_date_aviz.Clb_dataact.Text_simplu1.LostFocus` (:7603-7604) si
|
||||
`frm_date_aviz.Clb_dataireg...LostFocus` (:7610-7611): `poDate.zi_curs = poDate.dataact`,
|
||||
necondiionat (formularul aviz general nu are guard, dar si nu are RemoveObject pe zi_curs).
|
||||
- `frm_date_aviz_lucrare.Clb_dataact...LostFocus` (:8186-8187) si
|
||||
`...Clb_dataireg...LostFocus` (:8193-8194): idem, necondiionat - zi_curs NU e niciodata
|
||||
eliminat in aceasta clasa, deci sincronizarea merge mereu.
|
||||
- `frm_date_factura.Clb_dataact...LostFocus` (:9805-9808) si `...Clb_dataireg...` (:9824-9827):
|
||||
ACESTEA SUNT deja conditionate: `If Type('thisform.clb_zi_curs.visible')<>'U' ... zi_curs =
|
||||
dataact ... Endif` (comentariu `*!* modificare v 2.0.56`). Cand controlul e eliminat, sincronizarea
|
||||
se opreste - dar valoarea RAMASA de la Init/Reset nu se sterge, ramane cea de la creare.
|
||||
|
||||
**c) Consum efectiv in SQL (trimis catre Oracle, cursoare de articole):**
|
||||
`COMUN\programe\ofacturare.prg:266-308` (identic in `factureaza2`, :751-816) - alegerea SQL-ului de
|
||||
populare a articolelor se face pe `tnTip`, NU pe `in_valuta`:
|
||||
```
|
||||
Case Inlist(tnTip, 48, 49) -> cursor_articole_k(?poDate.zi_curs, ...)
|
||||
Case tnTip = 45 -> cursor_preturi(?poDate.zi_curs, ...)
|
||||
Case Inlist(tnTip, 1,22,5,29,7,10,23) -> cursor_preturi(?poDate.zi_curs, ...)
|
||||
Case Inlist(tnTip, 2,26,6,52) -> cursor_contract(?poDate.zi_curs, ...)
|
||||
Case Inlist(tnTip, 3,21,25,28,42,47) -> cursor_comanda(?poDate.zi_curs, ...)
|
||||
Case tnTip = 4 -> cursor_avize(...) [FARA zi_curs]
|
||||
Case Inlist(tnTip, 41) -> cursor_gestiune(?poDate.zi_curs, ...)
|
||||
Case tnTip = 30 -> cursor_aviz_nir(...) [FARA zi_curs]
|
||||
Case tnTip = 27 -> cursor_lucrare(?poDate.zi_curs, ...)
|
||||
Case Inlist(tnTip, 8,9,24) -> cursor_retur(?poDate.in_valuta, ...) [FARA zi_curs]
|
||||
```
|
||||
Descoperire cheie: pentru tipurile 8, 9, 24 (retur) SI 30 (aviz pe NIR), SQL-ul NU trimite deloc
|
||||
`poDate.zi_curs` catre Oracle - `zi_curs` gol sau completat nu are niciun efect pentru aceste
|
||||
tipuri. Acesta e motivul real pentru care eliminarea controlului la tip 8/9 e sigura (mai puternic
|
||||
decat simpla existenta a unei valori implicite).
|
||||
|
||||
Pentru tipurile care TRIMIT zi_curs, comportamentul in Oracle e diferit:
|
||||
- `cursor_preturi` (`docs\ff_2026_08_09_01_COMUN_PACK_FACTURARE.sql:2138+`) apeleaza NECONDITIONAT
|
||||
`pack_facturare.verifica_cursuri_valute(V_DATA_CURS, V_ID_UTIL)` (linia 2153). Aceasta procedura
|
||||
(`:16247-16274`) verifica cursul pentru TOATE valutele distincte din `FACT_VPRETURI_UTILIZATOR`
|
||||
ale utilizatorului curent (nu doar valuta documentului!) si arunca
|
||||
`RAISE_APPLICATION_ERROR(-20005, 'Nu este setat cursul din data de ... !')` daca oricare dintre
|
||||
ele nu are curs care sa acopere `V_DATA_CURS` - EXCLUDE explicit moneda nationala
|
||||
(`AND A.ID_VALUTA <> pack_facturare.nid_moneda_nationala`). Deci: chiar pe un document in LEI
|
||||
(`in_valuta=0`), daca utilizatorul are liste de preturi in valuta configurate si data trimisa
|
||||
(implicita sau nu) nu are curs setat, apelul PICA cu -20005 - INDIFERENT de vizibilitatea
|
||||
selectorului pe formular. Riscul nu vine din camp gol, ci din "camp cu o data pentru care nu
|
||||
exista curs in tabela CURS".
|
||||
- `cursor_articole_k` (`:3595-3701`) si `cursor_lucrare` (`:3173-3593`, foloseste `V_DATA_CURS`
|
||||
pentru comenzi_elemente) - `cursor_articole_k` NU apeleaza `verifica_cursuri_valute`, doar face
|
||||
`LEFT JOIN CURS ... WHERE DATA <= V_DATA_CURS AND DATA2 >= V_DATA_CURS` - daca nu gaseste, cade
|
||||
silentios pe `NVL(D.CURS,0)` (pret gresit, nu eroare). `cursor_lucrare` (:3186-3218) INSA face
|
||||
o verificare proprie, similara: daca articolele comenzii au valute fara curs pe `V_DATA_CURS`,
|
||||
arunca acelasi `-20005`.
|
||||
- Exista deja o rutina de recuperare la acest cod de eroare: `ofacturare.prg:313-317`
|
||||
```
|
||||
If lnSucces < 0
|
||||
AMESSAGEBOX(goExecutor.oPrelucrareEroare(), 16, "Eroare")
|
||||
If goExecutor.nEroare = 20005
|
||||
vizualizeaza_curs(poDate.zi_curs)
|
||||
ENDIF
|
||||
```
|
||||
Deci sistemul ANTICIPEAZA deja cazul "curs lipsa la data respectiva" si deschide un ecran de
|
||||
gestiune a cursurilor - independent de validarea din formularul de date.
|
||||
|
||||
**d) Afisare / etichetare (fara risc):**
|
||||
- `frm_facturare_articole.Init` (:15097-15098) si `frm_facturare_articole2.Init` (:19004-19005):
|
||||
`If !Empty(Nvl(poDate.zi_curs,{})) Then Thisform.lb_cursuri.Caption = "Curs valutar (" +
|
||||
Dtoc(poDate.zi_curs) + ")"` - deja tolereaza gol (nu afiseaza nimic), fara eroare.
|
||||
- `frm_date_factura.do_cauta_valuta` (:9344-9359) - dupa alegerea valutei, muta focusul pe
|
||||
`clb_zi_curs` DACA exista (`Type(...)<>'U'`), altfel pe `clb_serie_act`. Deja conditionat.
|
||||
- `onom_curs.vc2` (`ck_zi_curs`, `tx_zi_curs`) - ecran DIFERIT, de administrare a cursurilor
|
||||
valutare in sine (nu are legatura cu `poDate.zi_curs`; e o cautare "dupa ziua cursului" generica).
|
||||
|
||||
## 5. Valoarea implicita azi si de unde vine
|
||||
|
||||
Vine din `oDateFactura.Init`/`Reset`, necondiionat de tip sau de `in_valuta`:
|
||||
- `Init` (`ofacturare_comun.prg:235-247`): `ldData = Ttod(get_ora())`, ajustat la luna/anul curent
|
||||
de facturare (`gnAn`/`gnLuna`) daca `get_ora()` cade in alta luna; apoi `.zi_curs = ldData`.
|
||||
- `Reset` (`ofacturare_comun.prg:486-496`): `.zi_curs = .Data` (unde `.Data` a fost deja setat tot
|
||||
din `ldData`-ul curent).
|
||||
|
||||
Deci implicit `zi_curs` = data curenta (get_ora, ajustata la perioada de facturare deschisa), NU
|
||||
`Date()` brut si nu neaparat `dataact`/`dataireg` (desi acestea pornesc de la aceeasi `ldData`).
|
||||
Ulterior, cat timp controlul `clb_zi_curs` exista pe formular, orice editare a `dataact`/`dataireg`
|
||||
resincronizeaza `zi_curs = dataact` prin evenimentele `LostFocus` (vezi punctul 4b). Daca formularul
|
||||
ar ascunde controlul FARA sa elimine obiectul si fara sa goleasca proprietatea, campul ar ramane
|
||||
la valoarea implicita de la Init/Reset (sau la ultima valoare sincronizata inainte de ascundere).
|
||||
|
||||
## 6. Precedent: tip cu campul ascuns care se salveaza corect
|
||||
|
||||
DA, exista deja, dar in `frm_date_factura` (formularul de FACTURA), nu in `frm_date_aviz_lucrare`:
|
||||
|
||||
```
|
||||
COMUN\clase\ofacturare.vc2:9717-9722 [frm_date_factura.Init]
|
||||
*!* modificare v 2.0.56
|
||||
If Inlist(poDate.tip, 8, 9)
|
||||
lnHeight = lnHeight - .clb_zi_curs.Height
|
||||
laPozitii(.clb_zi_curs.TabIndex, 2) = 1
|
||||
.RemoveObject('clb_zi_curs')
|
||||
Endif
|
||||
*!* modificare v 2.0.56 ^
|
||||
```
|
||||
|
||||
Pentru tip 8 si 9 (facturi de retur - care pot fi chiar in valuta, vezi `ofacturare_comun.prg:248`:
|
||||
`INLIST(m.tnTip, 5,6,7,9,10,52) -> .in_valuta = 1`), controlul `clb_zi_curs` e eliminat COMPLET de
|
||||
pe formular, necondiionat de `in_valuta`, si documentul se salveaza corect. Motivele, in ordine de
|
||||
robustete:
|
||||
1. Validarea din `inainte_de_do_termin` (:9484) e deja garda cu `Type(...)<>'U'`, deci se
|
||||
auto-dezactiveaza cand controlul nu mai exista.
|
||||
2. SQL-ul de populare articole pentru tip 8/9 e `cursor_retur(?poDate.in_valuta,...)`
|
||||
(`ofacturare.prg:306-307`) - NU trimite deloc `poDate.zi_curs`, deci nu poate cauza -20005 din
|
||||
cauza acestui camp.
|
||||
3. Chiar daca ar fi trimis, `poDate.zi_curs` tot ar avea valoarea implicita de la Init/Reset
|
||||
(punctul 5) - nimic nu-l goleste la `RemoveObject`.
|
||||
|
||||
Aceasta e "reteta" cerinta de punctul 6: eliminarea vizuala e sigura pentru ca (a) validarea are
|
||||
deja garda pe existenta controlului, si (b) proprietatea `poDate.zi_curs` nu e niciodata golita -
|
||||
ramane pe implicitul din Init/Reset.
|
||||
|
||||
Pentru `frm_date_aviz_lucrare` (linia :8076) NU exista un tip cu campul ascuns - `clb_zi_curs` nu e
|
||||
eliminat pentru nici tip 27, nici tip 30. Motivul plauzibil: tip 27 (aviz pe lucrare) chiar
|
||||
foloseste `zi_curs` in `cursor_lucrare` pentru articolele comenzii (posibil in valuta), independent
|
||||
de `poDate.in_valuta` al documentului-aviz insusi - deci acolo campul NU e un candidat sigur pentru
|
||||
ascundere pe baza lui `in_valuta`. Pentru tip 30, SQL-ul (`cursor_aviz_nir`) nu foloseste `zi_curs`
|
||||
deloc, deci validarea de acolo e superflua dar inofensiva (campul e mereu populat implicit).
|
||||
|
||||
## Ramas de verificat
|
||||
|
||||
- Nu am gasit inca daca decizia 15 / S4d intentioneaza sa includa si `frm_date_aviz_lucrare` in
|
||||
formularul unificat, sau doar `frm_date_factura`/`frm_date_aviz`. Din cod, `frm_date_aviz_lucrare`
|
||||
e un formular de sine statator, fara control de valuta, folosit doar pentru tnTip 27 si 30 - daca
|
||||
planul S4d nu-l tinteste explicit, linia :8076 e in afara scopului imediat.
|
||||
- Nu am verificat ce se intampla in `cursor_articole_k` (tip 48/49) si `cursor_gestiune` (tip 41)
|
||||
fata de `verifica_cursuri_valute` - din citire, `cursor_articole_k` nu apeleaza acea procedura
|
||||
(cade silentios pe curs 0), dar nu am verificat `cursor_gestiune`.
|
||||
- Nu am verificat cum decide `frm_date_factura.Init` ce alte tipuri (in afara de 8,9) ar putea fi
|
||||
candidate pentru ascunderea lui `clb_zi_curs` conform deciziei 15 - doar am confirmat mecanismul
|
||||
existent si conditia dubla deja implementata la validare (in_valuta + Type<>'U').
|
||||
Reference in New Issue
Block a user