diff --git a/docs/S1_inventar_campuri_formular_unificat.md b/docs/S1_inventar_campuri_formular_unificat.md new file mode 100644 index 0000000..5abf4c1 --- /dev/null +++ b/docs/S1_inventar_campuri_formular_unificat.md @@ -0,0 +1,131 @@ +# S1 — Inventar camp-cu-camp: formularele de azi -> formularul unificat + +Livrabilul povestii **S1** din `docs\plan_13_unificare_formular_facturare.md`. Investigatie +read-only, documentatie pura (fara editari de cod, fara `git_sync.ps1`/`txt2vcx.ps1`, fara commit). + +Baza folosita, extinsa aici, nu refacuta: +- `docs\cercetare\inventar_controale_formulare.md` — etichete reale, controale, conditii de + vizibilitate din `frm_date_factura` / `frm_date_aviz` / `frm_alte_date`. +- `docs\cercetare\modifica_date_factura_parametri.md` — cei 15 parametri ai + `pack_facturare.modifica_date_factura`, mapati pe controalele din `frm_modifica_factura`. +- `docs\plan_13_unificare_formular_facturare.md`, sectiunile **I** (asezarea antetului), **I-bis** + (contractul lui `modifica_date_factura`), **F** (serie/numar/data — cale proprie), **G-bis** + (rutele de scriere) si povestea **S1**. + +Cele patru formulare de origine, prescurtate in tabel: `frm_date_factura`, `frm_date_aviz`, +`frm_modifica_factura`, `frm_alte_date`. Cele patru grupuri din formularul unificat (sectiunea I a +planului): **Identitatea documentului**, **Partener si sursa**, **Pliat — analitice**, **Pliat — +alte date** (cu subgrupurile ei: delegat/transport, adresa de facturare, incasare, text aditional, +si un subgrup nou, raportare). + +## Tabelul de campuri + +| Camp (eticheta reala) | Control azi + `fisier:linie` | Formularul de origine | Grupul unificat | Pliat / vizibil mereu | Conditii de vizibilitate azi | Ruta de scriere | Observatii | +|---|---|---|---|---|---|---|---| +| **Tip document** (fel document) | `Ct_clb_fdoc` = combo `_combobox1` FACTURA/PROFORMA/BON FISCAL (`ofacturare.vc2`, Init `9563-9796`); pe aviz, camp cautare `caut_ora.vcx` (Init `7354-7600`) — `inventar_controale_formulare.md` §3 | `frm_date_factura` + `frm_date_aviz` | Identitatea documentului | vizibil mereu | mereu vizibil (niciun `RemoveObject` gasit) | schimbarea tipului -> `do_schimba_tipdoc` realoca serie/numar (plan §B, §S3); **nu** e parte din cei 14 parametri `modifica_date_factura` | containerul difera intre factura (combo) si aviz (cautare) — de unificat la implementare (S3); linia exacta `ADD OBJECT` a controlului nu e in inventar, doar range-ul Init | +| **Serie document** | `Clb_serie_act` label "Serie document" (factura) / fara label explicit override (aviz), container `clb_serie_act` (`serii_numere.vcx`) — inventar §3; la editare: `txtSerieAct` (`ofacturare_comun.vc2:5605-5614`), gating `chkSerieAct` (`:5405-5416`, activare `:5743-5750`) | `frm_date_factura`/`frm_date_aviz` (creare) + `frm_modifica_factura` (editare azi, prin bifa) | Identitatea documentului | vizibil mereu | eliminat daca `poDate.rezultat_serii` nu e in `(1,2,3)` — inventar §3 | pe loc, `modifica_date_factura` (`V_SERIE_ACT`), regim **conditionat** (doar daca `NOT NULL` si difera de curent), propaga in `DOCUMENTE`/`ACT`/`IREG_PARTENERI`/`JV2007`/`RUL` dupa `ID_FACT` — `modifica_date_factura_parametri.md:29`, plan §F/§I-bis | decizia 9: cele patru bife dispar; in unificat campul e blocat/deblocat de `but_modifica`, **fara** conditia de azi `!EMPTY(numar_act)` (plan I:531-535) | +| **Numar document** | `Clb_nract` = "Numar document"/"Nr. documentului", `InputMask=get_mask(14,0)` — inventar §3; editare: `txtNrAct` (`:5594-5603`), gating `chkNrAct` (`:5392-5403`, activare `:5743-5750`) | `frm_date_factura`/`frm_date_aviz` + `frm_modifica_factura` | Identitatea documentului | vizibil mereu | mereu prezent | pe loc, `modifica_date_factura` (`V_NUMAR_ACT`), conditionat, propaga in aceleasi 5 tabele dupa `ID_FACT` — `modifica_date_factura_parametri.md:28` | idem decizia 9 | +| **Data document** (data act) | `Clb_dataact` = "Data document"/"Data documentului" — inventar §3; editare: `txtDataAct` (`:5572-5581`), gating `chkDataAct` (`:5353-5364`) | `frm_date_factura`/`frm_date_aviz` + `frm_modifica_factura` | Identitatea documentului | vizibil mereu | mereu prezent | pe loc, `modifica_date_factura` (`V_DATA_ACT`), conditionat — `modifica_date_factura_parametri.md:26` | idem decizia 9 | +| **Data scadenta** | `Clb_data_scadenta` = "Data scadenta" — **nu exista pe aviz**; editare: `txtDataScad` (`:5583-5592`), gating `chkDataScad` (`:5366-5377`) | `frm_date_factura` (doar) + `frm_modifica_factura` | Identitatea documentului | **dezactivat**, nu eliminat, cand `gnScadentaAutomata=1` (`ofacturare.vc2:9713-9715`) | vezi coloana anterioara | pe loc, `modifica_date_factura` (`V_DATA_SCAD`), conditionat — `modifica_date_factura_parametri.md:27` | specific facturii; idem decizia 9 | +| **Data curs valutar** (zi curs) | `Clb_zi_curs` = "Data curs valutar"/"Data cursului valutar" — inventar §3 | `frm_date_factura` + `frm_date_aviz` | Identitatea documentului | eliminat pe factura la retur (`tip in(8,9)`, `ofacturare.vc2:9718-9722`) | decizia 15 (S4d) schimba conditia: apare doar cand documentul nu e retur **si** (e in valuta **sau** a intrat pe grid un articol cu pret in valuta) | **nu** e parte din cei 14 parametri `modifica_date_factura` — trimis catre cursoarele de articole (`poDate.zi_curs`, `ofacturare.prg:272-303`), nu e scris separat in antet | **vezi Neclarificate** — nicio ruta de scriere documentata pentru modificarea acestui camp pe un document deja emis | +| **Valuta** | `Ct_clb_valuta` = "Valuta" — **nu exista pe aviz** | `frm_date_factura` (doar) | Identitatea documentului | eliminat daca `poDate.in_valuta=0` (`ofacturare.vc2:9725-9732`) | — | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Client** (nume client) | `Ct_clb_nume_client` = "Nume client" (factura); eticheta schimbata pe aviz ("Retur de la"/"Gestiune sursa" pe transfer) | `frm_date_factura` + `frm_date_aviz` | Partener si sursa | vizibil mereu | eticheta variaza pe tip transfer (aviz) | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Cod fiscal** (derivat) | `txtCodFiscal`/`lblCodFiscal`, `ReadOnly` | `frm_date_factura` (doar) | Partener si sursa | vizibil mereu | langa client | niciuna — derivat, doar afisare | readonly | +| **Sold curent client** (derivat) | `txtSoldLei`/`lblSoldLei`, populat din `GetSoldClient()` daca `poDate.id_client<>0` | `frm_date_factura` (doar) | Partener si sursa | vizibil mereu | doar cand `id_client<>0` | niciuna — derivat, doar afisare | readonly | +| **Verificare ANAF** (buton, nu camp) | `But_verifica1` | `frm_date_factura` (doar) | Partener si sursa | vizibil mereu | — | actiune, nu camp de antet | — | +| **Sursa** ("Altele" — eticheta dinamica) | `Ct_clb_altele`, `.do_schimba_explicatia(...)` — eticheta comuta intre "Nr. contract"/"Nr. comanda"/"Nr. factura"/"Nr. facturi"/"Locatie" (`ofacturare.vc2:9633-9643`) | `frm_date_factura` + `frm_date_aviz` | Partener si sursa | eliminat complet daca `gnScadereStoc=0 and tip in(1,5,10)` fara copiere | vezi coloana anterioara | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Gestiune sursa** (init) | `Ct_clb_gestiune_init` = "Gestiune sursa", ToolTipText despre stocurile tuturor gestiunilor | `frm_date_factura` (doar) | Partener si sursa | eliminat impreuna cu Responsabil in majoritatea cazurilor `gnScadereStoc=0` | — | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Politica de preturi** | `Ct_clb_politici_preturi` = "Politica de preturi" | `frm_date_aviz` (doar) | Partener si sursa | eliminat pe majoritatea tipurilor cu comanda | — | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Tip venit/cheltuiala** | `Ct_clb_venchelt` = "Venit / cheltuiala" | `frm_date_factura` + `frm_date_aviz` | Pliat — analitice | eliminat pe aviz pentru `tip=23,41,25` (`ofacturare.vc2:7438-7461`) | vezi coloana anterioara | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Sectie** | `Ct_clb_sectie` = "Sectie" | `frm_date_factura` + `frm_date_aviz` | Pliat — analitice | intotdeauna prezent (niciun `RemoveObject` gasit) | — | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Responsabil** | `Ct_clb_responsabil` = "Responsabil" | `frm_date_factura` + `frm_date_aviz` | Pliat — analitice | eliminat cand `gnScadereStoc=0` sau `tip in(4,7,8,9,48,49)` — factura: `ofacturare.vc2:9646-9707` | vezi coloana anterioara | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Lucrare** | `Ct_clb_lucrare` = "Lucrare" | `frm_date_factura` + `frm_date_aviz` | Pliat — analitice | nu s-a gasit eliminare conditionata | — | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Ruta** | `Ct_clb_ruta` — **doar** `frm_modifica_factura` (`ofacturare_comun.vc2:5510-5526`, cautare `5695-5721`); **nu exista** in `frm_alte_date` sau `frm_date_factura`/`frm_date_aviz` | `frm_modifica_factura` (doar) | Pliat — alte date > delegat/transport | pliat | mereu editabil (fara restrictie in cod — I:521-522) | pe loc, `modifica_date_factura` (`V_ID_RUTA`), regim **neconditionat** — `modifica_date_factura_parametri.md:16` | singurul din cei 14 fara echivalent in `frm_alte_date`; **vezi Neclarificate** pentru introducerea initiala | +| **Delegat** | `Ct_clb_delegat` = "Delegat" (`ferestre_cere_date.vc2:2553`, `frm_alte_date`); acelasi nume in `frm_modifica_factura` (`ofacturare_comun.vc2:5473-5489`, cautare `5654-5678`) | `frm_alte_date` + `frm_modifica_factura` | Pliat — alte date > delegat/transport | pliat | mereu editabil | pe loc, `modifica_date_factura` (`V_ID_DELEGAT`), neconditionat — `modifica_date_factura_parametri.md:17` | `But_modifica1` din `frm_alte_date` (`ferestre_cere_date.vc2:2343`) deschide `nom_parteneri_modifica` pe delegatul curent — actiune, nu camp separat | +| **Auto / Masina** | `Ct_clb_masina` = "Masina" (`ferestre_cere_date.vc2:2569`); acelasi nume in `frm_modifica_factura` (`5491-5508`, cautare `5680-5693`) | `frm_alte_date` + `frm_modifica_factura` | Pliat — alte date > delegat/transport | pliat | mereu editabil | pe loc, `modifica_date_factura` (`V_ID_MASINA`), neconditionat — `modifica_date_factura_parametri.md:18` | — | +| **Agent** | `Ct_clb_agent` = "Agent" (`ferestre_cere_date.vc2:2537`); acelasi nume in `frm_modifica_factura` (`5455-5471`, cautare `5639-5652`) | `frm_alte_date` + `frm_modifica_factura` | Pliat — alte date > delegat/transport | pliat | mereu editabil | pe loc, `modifica_date_factura` (`V_ID_AGENT`), neconditionat — `modifica_date_factura_parametri.md:19` | — | +| **Data si ora expedierii** | `Clb_dataora_exp` = "Data si ora expedierii" (`ferestre_cere_date.vc2:2443`); `frm_modifica_factura`: `Clb_dataora_exp.Text_simplu1`, `ControlSource="poRec.dataora_exp"` (`5437-5453`) | `frm_alte_date` + `frm_modifica_factura` | Pliat — alte date > delegat/transport | pliat | mereu editabil | pe loc, `modifica_date_factura` (`V_DATAORA_EXP`), neconditionat — `modifica_date_factura_parametri.md:20` | validat **obligatoriu** in `inainte_de_do_termin` (`ofacturare_comun.vc2:5723-5734`) — singurul camp din grup cu validare de continut, nu doar de scriere | +| **Adresa de facturare** | `clb_adresa_facturare` = "Adresa facturare" (`ferestre_cere_date.vc2:2422`); `frm_modifica_factura` (`5418-5435`, cautare `5621-5637`) | `frm_alte_date` + `frm_modifica_factura` | Pliat — alte date > adresa de facturare | pliat | mereu editabil | pe loc, `modifica_date_factura` (`V_ID_FACTURARE`), neconditionat — `modifica_date_factura_parametri.md:21` | seteaza si `poRec.adresa_facturare` (text derivat), nu doar id-ul | +| **Text aditional** | `Ed_tx_simplu1` = "Text aditional (max. 1000 caractere)" (`ferestre_cere_date.vc2:2585`); `frm_modifica_factura`: `Ed_tx_simplu1._EDBASE1`, `MaxLength=1000` (`5528-5551`) | `frm_alte_date` + `frm_modifica_factura` | Pliat — alte date > text aditional | pliat | mereu editabil | pe loc, `modifica_date_factura` (`V_TEXT_ADITIONAL`), neconditionat, **fara** `NVL` — `modifica_date_factura_parametri.md:23` | comentariul VFP la `:4575` spune "MAXIM 250 CARACTERE" dar `MaxLength` control e 1000 — contradictie din sursa, neverificata mai departe | +| **Mod incasare** (radiogrup) | `opt_incasat` — 4 optiuni: Fara incasare / Chitanta / Bon fiscal / POS Card (`ferestre_cere_date.vc2:2638`), masina de stari `actualizeaza_tipincasare` (`2698-2856`) | `frm_alte_date` (doar) | Pliat — alte date > incasare | pliat | comuta vizibilitatea celorlalte campuri din grupul incasare | **nu** e parte din cei 14 parametri `modifica_date_factura` | **vezi Neclarificate** — probabil scris la emitere, dovada nu a fost gasita in materialele citite | +| **Casa** (-> "Banca POS" pe POS) | `Cb_casa` (`ferestre_cere_date.vc2:2362`) | `frm_alte_date` (doar) | Pliat — alte date > incasare | pliat, ascuns cand `opt_incasat=1` | vezi `actualizeaza_tipincasare` | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Serie chitanta** | `Clb_serie_chit` (`ferestre_cere_date.vc2:2503`) | `frm_alte_date` (doar) | Pliat — alte date > incasare | pliat, vizibil doar la Chitanta | `Thisform.clb_serie_chit.genereazaNumar()` la comutare (`2783`) | **nu** e parte din cei 14 parametri — alocare de numar propriu, nu antet document | **vezi Neclarificate** | +| **Nr. chitanta / Nr. bon** | `Clb_nrchit` (`ferestre_cere_date.vc2:2480`) | `frm_alte_date` (doar) | Pliat — alte date > incasare | pliat, eticheta variaza (Chitanta/Bon fiscal/POS) | `do_aloca_nr_bon`/`do_aloca_nr_pos` la comutare (`2807`, `2844`) | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Incasat** (suma) | `Clb_incasat` (`ferestre_cere_date.vc2:2461`) | `frm_alte_date` (doar) | Pliat — alte date > incasare | pliat, ascuns doar la `opt_incasat=1` | — | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Modifica bon** (buton, nu camp) | `cmdModificaBon` -> `do_modifica_bon` -> `viz_config_serii_complet WITH 3` (`ferestre_cere_date.vc2:2526`, `3001-3010`) | `frm_alte_date` (doar) | Pliat — alte date > incasare | pliat, vizibil doar la Bon fiscal | — | actiune, nu camp de antet | — | +| **POS** (bifa) | `chkPOS` (`ferestre_cere_date.vc2:2409`) | `frm_alte_date` (doar) | Pliat — alte date > incasare | pliat, vizibil doar la Bon fiscal | — | **nu** e parte din cei 14 parametri | **vezi Neclarificate** | +| **Detaliat** / **Listare detaliata** | `chkDetaliat` in `frm_alte_date` (`ferestre_cere_date.vc2:2396`, vizibil doar la Bon fiscal); control **cu acelasi nume** in `frm_modifica_factura` (`ControlSource="poRec.listare_detaliata"`, `5379-5390`) | `frm_alte_date` + `frm_modifica_factura` (posibil, vezi observatie) | Pliat — alte date > incasare (azi) / candidat mutare langa Text aditional | pliat | vizibil doar la Bon fiscal (`frm_alte_date`) | pe loc, `modifica_date_factura` (`V_LISTARE_DETALIATA = NVL(...,0)`), neconditionat — `modifica_date_factura_parametri.md:22` | **vezi Neclarificate** — nu s-a confirmat ca cele doua controale `chkDetaliat` scriu acelasi camp Oracle (`VANZARI.LISTARE_DETALIATA`); merse din numele identic, nu din dovada de cod citita in ambele locuri | +| **Tip factura** (SAF-T) | `cboTipFactura` in `frm_alte_date` (`ferestre_cere_date.vc2:2378`, grup incasare); control **cu acelasi nume** in `frm_modifica_factura` (`ControlSource="poRec.tip_saft"`, `RowSource` din `saft_tip_facturi`, `Visible = m.gl406`, `5335-5351`, `5739-5741`) | `frm_alte_date` + `frm_modifica_factura` (posibil) | Pliat — alte date > **grup nou "raportare"** (langa eFactura) — I-bis:583,588 | pliat | `frm_modifica_factura`: vizibil doar cand `gl406` (`COMUN\programe\oinit_optiuni.prg:524`) | pe loc, `modifica_date_factura` (`V_TIP_SAFT`), neconditionat — `modifica_date_factura_parametri.md:24` | **vezi Neclarificate** — `RowSource` al `cboTipFactura` din `frm_alte_date` nu a fost verificat, deci legatura cu `V_TIP_SAFT` e prin nume, nu prin cod citit in ambele locuri | +| **eFactura** | **niciun control, nicaieri** — `poRec.efactura` vine doar din `Scatter` initial (`ofacturare_comun.vc2:4538-4564`) sau e fortat `0` la selectie multipla (`:4576`) | — (nu exista azi) | Pliat — alte date > **grup nou "raportare"** — I-bis:583,588 | pliat, control nou | N/A | pe loc, `modifica_date_factura` (`V_EFACTURA`), neconditionat — `modifica_date_factura_parametri.md:25,88` | **camp fara control azi, nicaieri — primeste unul nou** in formularul unificat (cerinta explicita a sarcinii) | +| **cele patru bife de protectie a antetului** (`chkSerieAct`, `chkNrAct`, `chkDataAct`, `chkDataScad`) | `frm_modifica_factura`: `5405-5416`, `5392-5403`, `5353-5364`, `5366-5377`; activare comuna `Init` `5743-5750` | `frm_modifica_factura` (doar) | **DISPARE** — inlocuit cu un singur `but_modifica` la nivel de panou de antet (plan I:524-535) | — | fiecare bifa `Enabled` doar daca `!EMPTY(NVL(poRec.numar_act,0))` | N/A — mecanism de deblocare a UI, nu camp de date | decizia 9: se abandoneaza **si conditia lor**; cu antetul deschis in unificat, toate campurile sunt editabile fara exceptie si fara conditie de stare | + +## Campuri fara control azi, nicaieri + +- **`V_EFACTURA`** — vezi randul dedicat mai sus. Singurul camp din cei 14 parametri fara echivalent + in niciunul din cele patru formulare de azi; primeste control nou in formularul unificat, in + grupul nou "raportare" din sectiunea pliata, langa `V_TIP_SAFT`. + +## Ce dispare + +- **Cele patru bife** `chkSerieAct`/`chkNrAct`/`chkDataAct`/`chkDataScad` din `frm_modifica_factura` + — decizia 9 le elimina cu totul, inlocuite de un singur `but_modifica` la nivel de antet. Conditia + lor de activare (`!EMPTY(numar_act)`) se abandoneaza si ea, nu se transpune pe campuri. +- **`frm_articol_factura`** (dialogul de detaliu pe linie, `ofacturare.vc2:1108-2659`) — desfiintat + odata cu unificarea (plan §B); campurile lui de discount (procent + valoare unitara) se muta ca + **coloane in grid** (decizia 14, S4c), nu ca parte a antetului — nu apar in acest tabel pentru ca + nu sunt campuri de antet. +- **`frm_alte_date`** ca dialog separat nu dispare in etapa I (S3b: "`frm_alte_date` nu se sterge cat + timp calea veche mai e in uz"), dar cele patru grupuri ale lui se muta *si* in formularul unificat, + ca sectiune pliata. + +## Ruta de scriere pe loc vs. regenerare (rezumat din G-bis) + +| Ce s-a schimbat | Cum se scrie | +|---|---| +| Serie, numar, data, scadenta, delegat, auto (masina), agent, adresa facturare, text aditional, ruta, `dataora_exp`, `listare_detaliata`, `tip_saft`, `efactura` (cei 14 din I-bis) | pe loc, `modifica_date_factura` — propaga dupa `ID_FACT` | +| Explicatia si `taxcode` pe o linie | pe loc, `modifica_explicatie_articol` (`PACK_FACTURARE:14464-14472`) | +| Cantitati, preturi, linii adaugate/sterse, discount, gestiune, cota TVA | **regenerare** | +| Tip venit/cheltuiala, sectie, responsabil, lucrare, client, cod fiscal, sold, sursa/altele, gestiune sursa, politica de preturi, valuta, zi curs, tip document, mod incasare + campurile lui | **neclarificat** — nu apar in cei 14 parametri ai `modifica_date_factura` si nu s-a gasit alta ruta explicita in materialele citite (vezi sectiunea urmatoare) | + +## Neclarificate + +Campuri pentru care nu s-a gasit control azi, sau nu s-a gasit ruta de scriere la modificarea unui +document deja emis. Nu s-a inventat nimic — fiecare e marcat aici in loc de o presupunere in tabel. + +1. **Ruta de scriere lipsa — analiticele antetului**: `Ct_clb_venchelt` (venit/cheltuiala), + `Ct_clb_sectie` (sectie), `Ct_clb_responsabil` (responsabil), `Ct_clb_lucrare` (lucrare). Niciunul + nu e parte din cei 14 parametri `modifica_date_factura` (I-bis). Nu s-a gasit alt apel care sa le + scrie la modificarea unui document existent, in materialele citite pentru S1. +2. **Ruta de scriere lipsa — identitate/sursa in antet, alta decat serie/numar/data**: `Ct_clb_fdoc` + (tip document — schimbarea felului la un document deja emis, distinct de `do_schimba_tipdoc` la + creare), `Ct_clb_valuta`, `Clb_zi_curs`, `Ct_clb_nume_client`, `Ct_clb_altele` (sursa), + `Ct_clb_gestiune_init`, `Ct_clb_politici_preturi` (aviz). Aceeasi observatie: fara parametru + dedicat in `modifica_date_factura`, fara alta ruta gasita. +3. **Ruta de scriere lipsa — grupul de incasare**: `opt_incasat`, `Cb_casa`, `Clb_serie_chit`, + `Clb_nrchit`, `Clb_incasat`, `chkPOS`. Nu apar in cei 14 parametri. Alocarea/dezalocarea numerelor + de chitanta/bon/POS e documentata (`actualizeaza_tipincasare`), dar scrierea efectiva a valorilor + de incasare in Oracle la modificarea unui document deja emis nu a fost gasita in materialele citite + pentru S1 — posibil sa se intample doar la emitere initiala, nu la editare ulterioara. +4. **Ambiguitate `chkDetaliat`**: exista cate un control cu acest nume atat in `frm_alte_date` + (`ferestre_cere_date.vc2:2396`, vizibil doar la Bon fiscal) cat si in `frm_modifica_factura` + (`ofacturare_comun.vc2:5379-5390`, `ControlSource="poRec.listare_detaliata"`, mereu vizibil). Nu + s-a verificat in cod daca cele doua scriu efectiv acelasi camp `VANZARI.LISTARE_DETALIATA` sau + sunt doua concepte distincte cu nume coincident — merjul in tabel e prin numele identic, nu prin + citirea ambelor `ControlSource`. +5. **Ambiguitate `cboTipFactura`**: acelasi tip de coincidenta — `frm_alte_date` + (`ferestre_cere_date.vc2:2378`, in grupul de incasare) si `frm_modifica_factura` + (`ControlSource="poRec.tip_saft"`, `5335-5351`). `RowSource`-ul din `frm_alte_date` nu a fost + deschis pentru a confirma ca populeaza acelasi `saft_tip_facturi`. +6. **Ruta `V_ID_RUTA` la introducerea initiala**: controlul `Ct_clb_ruta` exista doar in + `frm_modifica_factura` — nu a fost gasit un control echivalent pe niciunul din celelalte trei + formulare (creare document). Nu e clar din materialele citite unde/cum se stabileste ruta la + introducerea initiala a unui document (posibil implicit din utilizator/sesiune, nesetat prin UI). +7. **Linia exacta de definire a controalelor din tabelul §3 al inventarului**: `inventar_controale_formulare.md` + nu contine coloana `fisier:linie` pentru tabelul de antet (spre deosebire de tabelele de butoane) — + citatele de mai sus folosesc range-urile de `Init` (`ofacturare.vc2:9563-9796` / `:7354-7600`) si + liniile explicite date in prealabil pentru conditiile de vizibilitate, nu linia `ADD OBJECT` a + fiecarui control individual. Daca implementarea are nevoie de linia exacta, trebuie cautata cu + `vfp_symbols.ps1 -Where`. +8. **Drept de utilizator**: inventarul de baza semnaleaza deja ca nu a verificat daca vizibilitatea + controalelor de antet mai depinde si de `gcAcces`/drepturi, dincolo de `poDate.tip`/setari globale + de firma. Nepreluat aici, ramane deschis. diff --git a/docs/brief_s5_test_scriere_reala.md b/docs/brief_s5_test_scriere_reala.md new file mode 100644 index 0000000..13db766 --- /dev/null +++ b/docs/brief_s5_test_scriere_reala.md @@ -0,0 +1,101 @@ +# Briefing — S5, testul cu scriere reala in Oracle + +Misiune pentru un agent proaspat. Se citeste **impreuna cu** `docs\handoff_s5.md` (starea blocului) si +`COMUN\docs\reguli_lucru.md` (regulile de livrare si testare). Nu relua cercetarea — e facuta. + +## De ce exista acest test + +Cele sase suite headless dovedesc **doar absenta regresiei**. Lucrul care conteaza in S5 nu e atins de +niciuna dintre ele, pentru ca se intampla in Oracle, in interiorul unei tranzactii: + +`pack_facturare.actualizeaza_vanzari` (`PACK_FACTURARE.pck:16020-16023`) face +`UPDATE VANZARI_DETALII SET STERS = 0` pe **tot documentul**, fara garda. E apelata din +`finalizeaza_modificare_nota`. Consecinta dubla, care e chiar motivul deciziei 38: + +1. o linie marcata stearsa de VFP **inainte** de `finalizeaza_modificare_nota` s-ar pierde tacut; +2. orice linie stearsa la o editare **anterioara** e inviata la fiecare salvare ulterioara — deci + stergerea de linie n-ar deveni niciodata definitiva. + +Idiomul adoptat („marcheaza tot sters, invie ce ramane", scris **dupa** `finalizeaza_modificare_nota`) +trebuie sa corecteze ambele. **Asta e ce are de dovedit testul, nu altceva.** + +## Ordinea care se testeaza (decizia 38) + +O singura tranzactie manuala: + +``` +do_deschide_tranzactie() + OSCRIE_IN_FISIERE(2, ...) -- sterge nota veche + OSCRIE_IN_FISIERE(0, ...) -- scrie nota noua + finalizeaza_modificare_nota(...) -- realiniaza COD, RESETEAZA STERS = 0 + ScrieArticoleFacturaEditate(id_vanzare) -- NOU + pack_facturare.recalculeaza_totaluri_vanzari(id_vanzare, discount) -- NOU +do_inchide_tranzactie(Iif(lnSucces < 0, 2, 1)) +``` + +Agatarea reala e in `ofacturare_comun.vc2:3828` si `comun.vc2:2491`, acelasi bloc de 3 randuri. + +## Ce trebuie sa dovedeasca, punct cu punct + +**Trecerea 1 — o editare cu o linie stearsa si o linie adaugata:** + +- linia marcata stearsa in `tvd` ajunge cu `STERS = 1` in `VANZARI_DETALII` **dupa** ce s-a intors + `finalizeaza_modificare_nota` (deci resetul ei nu o invie); +- linia noua (`id_vanzare_det = 0`) ajunge cu `INSERT`, cu `ID_VANZARE_DET` alocat de trigger + (`TRG_VANZARI_DET_BEFOINS`), si cu `PRET_ACHIZITIE` exact valoarea tastata; +- liniile pastrate raman active (`STERS = 0`) cu cantitatile/preturile editate; +- `PRET_ACHIZITIE` pe liniile **existente** ramane **neatins** (se omite din `SET`-ul de `UPDATE`) — + se verifica valoarea dinainte vs. dupa, pe o linie careia i s-a schimbat cantitatea; +- totalurile din `VANZARI` corespund liniilor active dupa recalcul. + +**Trecerea 2 — a doua editare a ACELEIASI facturi, fara nicio modificare:** + +- linia stearsa la trecerea 1 **ramane stearsa**. Asta e consecinta 2 din decizia 38 si e singurul + mod de a dovedi ca idiomul repara resetul. **Fara aceasta trecere, testul nu si-a atins scopul.** + +## Cum se verifica + +**Nu din logul testului.** Dupa fiecare trecere, verificare **independenta prin `sqlplus`** pe +`MARIUSM_AUTO@ROA_CENTRAL`, cu `SELECT`-uri pe `VANZARI` si `VANZARI_DETALII`. Tiparul exista deja si +a fost folosit cu succes: `COMUN\utile\Teste\editare_factura\test_writeback_buton1.prg` plus +`docs\cercetare\rec_test_writeback.md` — **citeste-le intai**, sunt modelul de urmat (au facut COMMIT +real pe `id_vanzare = 1048`, cu 5 verificari confirmate independent). + +Cifrele se **numara din loguri**, nu se citesc din impresie. Dovada ca rularea a ajuns la capat e +linia finala a suitei (`REZULTAT` sau `done`, dupa tipar). + +## Aprobare si costuri + +**Scrierea reala e aprobata de Marius** (09.08.2026), aprobarea e consemnata in `handoff_s5.md`. +**Consuma date de test ireversibil**: `cod`-ul documentului se realoca la fiecare salvare (la testul +precedent, `1140886 -> 1140893 -> 1140894`). Alege un document de test, **nu** unul folosit ca baza de +regresie de alte suite daca poti evita, si **consemneaza in raport `cod`-urile consumate**. + +## Reguli care nu se incalca + +- **`COMUN\clase\ofacturare.vc2` nu se atinge.** +- `actualizeaza_vanzari` si `PACK_CONTAFIN` **nu se modifica**. +- **Fara commit** — nici git, nici SVN. +- **Un singur scriitor pe fisier.** `omodificari.vc2` e inchis (livrare finala), nu-l atinge. + Fisierele tale sunt in `COMUN\utile\Teste\editare_factura\`. +- **Nu rula `git_sync.ps1`** decat daca text si binar sunt sincrone. +- **Nu porni VFP daca mai exista un `vfp9.exe` viu** care nu e al tau — verifica intai. +- Daca gasesti un defect real in codul de productie, **nu-l repara singur**: raporteaza-l cu + `fisier:linie` + citatul minim si asteapta. + +## Livrabile — se scriu PE DISC, la aceste cai exacte + +1. `COMUN\utile\Teste\editare_factura\test_s5_scriere_reala.prg` — suita. +2. `D:\ROA\ROAFACTURARE\docs\cercetare\rec_s5_scriere_reala.md` — raportul: ce s-a dovedit, ce nu, + `SELECT`-urile de verificare cu rezultatele lor, `cod`-urile consumate, cifrele din loguri. +3. `D:\ROA\ROAFACTURARE\docs\diff_s5_test_scriere_reala.patch` — diff-ul. + +Raspunsul catre orchestrator e **scurt**: „GATA" + cifrele + ce nu s-a dovedit. **Nu trimite raportul +in text** — el sta pe disc. + +## Predarea contextului (REGULA ZERO) + +Daca ajungi la **~200-250k** context: **opreste-te**, adu tot la o stare consistenta pe disc, scrie +`D:\ROA\ROAFACTURARE\docs\handoff_s5_scriere_reala.md` cu **starea** (ce e facut, ce nu, ce e +periculos: tranzactie deschisa, proces viu, date consumate), si anunta. Nu continua „ca mai e putin" — +si **nu lasa niciodata o tranzactie deschisa** in Oracle. diff --git a/docs/cercetare/audit_vanzari_creare_modificare_stergere.md b/docs/cercetare/audit_vanzari_creare_modificare_stergere.md new file mode 100644 index 0000000..17d1f92 --- /dev/null +++ b/docs/cercetare/audit_vanzari_creare_modificare_stergere.md @@ -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. diff --git a/docs/cercetare/buton_comutator_picture.md b/docs/cercetare/buton_comutator_picture.md new file mode 100644 index 0000000..c018ea8 --- /dev/null +++ b/docs/cercetare/buton_comutator_picture.md @@ -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`. diff --git a/docs/cercetare/canal_cont_venit_fara_politica.md b/docs/cercetare/canal_cont_venit_fara_politica.md new file mode 100644 index 0000000..4e98556 --- /dev/null +++ b/docs/cercetare/canal_cont_venit_fara_politica.md @@ -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.`, **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 ELSE 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 () 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. diff --git a/docs/cercetare/cont_venit_articol_fara_politica.md b/docs/cercetare/cont_venit_articol_fara_politica.md new file mode 100644 index 0000000..3ad23a4 --- /dev/null +++ b/docs/cercetare/cont_venit_articol_fara_politica.md @@ -0,0 +1,128 @@ +# Contul inregistrat pe o linie de vanzare, cu/fara politica de pret + +**Raspuns scurt**: nu exista, nicaieri in codul cercetat (VFP `ofacturare*` + Oracle +`pack_facturare`), un "cont de venit" (707/704/706/708) legat de linia de factura. Singurul cont +propagat pe linie e coloana `CONT` din `VANZARI_DETALII`/`VANZARI_DETALII_TEMP` (varchar 4 +caractere) — si aceasta e contul de **gestiune/stoc** (371 marfuri, 301-303 materii prime, 357 +custodie etc.), folosit pentru descarcarea de gestiune, nu un cont de venit. El **nu vine din +politica de pret** (`CRM_POLITICI_PRET_ART` nu are coloana `CONT` — verificat, nicio +`CREATE`/`ALTER TABLE CRM_POLITICI_PRET_ART ADD CONT` in `D:\ROA\DATABASE\SCRIPTURI_CLAR`), ci din +nomenclatorul de articole si din lotul de stoc ales. Asta inseamna ca "adaugarea directa din +nomenclator, fara politica de pret" **nu schimba mecanismul de completare a acestui camp** — el +oricum nu depinde de politica azi. Ramane neverificat unde/daca se decide efectiv un cont de venit +707/704/706/708 pentru nota contabila (nu e in `pack_facturare`). + +## 1. Unde e stocat / de unde se ia campul `CONT` + +- **Tabel/coloana tinta**: `VANZARI_DETALII_TEMP.CONT` si `VANZARI_DETALII.CONT`, populate prin + `pack_facturare.adauga_articol_factura` / `_deviz` / `_stoc` + (`ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql:4690,4711,4735` / `:4761,4792,4814` / + `:5013,5247,5277`). +- **Sursa 1 — `NOM_ARTICOLE.CONT`** (prin view `vnom_articole`/`vnom_articole_crm`): coloana `a.cont` + e adusa in grila de cautare a articolelor, `COMUN\programe\ocautare.prg:1671,1683,1687` (`caut_articol`, + folosit de formularul unificat de facturare la alegerea articolului). Confirmata ca fiind pe + `NOM_ARTICOLE` prin `alter table NOM_ARTICOLE add ... A.CONT ...` (`D:\ROA\DATABASE\SCRIPTURI_CLAR\2019\12\ff_2019_12_20_02_COMUN.sql:12`, coloana veche, prezenta din 2009-2010). +- **Sursa 2 — `STOC.CONT`** (contul de pe lotul de stoc): `pack_facturare.cursor_gestiuni_articol` + (`ff_...:4358-4452`) — `SELECT ... A.CONT ... FROM STOC A2 ...` — folosit cand articolul e + **gestionabil** si se alege un lot din stoc (`frm_facturare_articole.do_alege_stoc`, + `COMUN\clase\ofacturare.vc2:13239-13345`, care seteaza `poArticol.Cont = Alltrim(Cont)` la + `:13345` din cursorul intors de Oracle). +- **Sursa 3 — `NOM_GESTIUNI.CONT`**: fallback cand nu exista lot de stoc, vezi punctul 2 + (`cursor_gestiuni_articol_stoc0`). +- **Politica de pret** (`CRM_POLITICI_PRET_ART`): are `PRET`, `PROC_TVAV`, `ID_VALUTA`, + `PRETFTVA` (vazute in `adauga_articol_factura`, declaratiile `V_PRET`, `V_PROC_TVAV` etc. la + `ff_...:5033-5036`) — **nicio coloana de cont**. Cautarea in `SCRIPTURI_CLAR` pentru + `ALTER TABLE CRM_POLITICI_PRET_ART ADD ... CONT` nu a gasit nimic. +- Nu exista `CONT_VENIT`/`ID_CONT_VENIT`/`COD_CONT` nicaieri in + `ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` (grep pe fisierul intreg, 17020 linii — zero + potriviri) si nici in `NOM_ARTICOLE`/`CRM_POLITICI_PRET_ART` (cautat in `SCRIPTURI_CLAR`). + +## 2. Cum se decide efectiv la scriere — `cursor_gestiuni_articol` / `_stoc0` + +Cand articolul e gestionabil si exista stoc, `cursor_gestiuni_articol` (`ff_...:4358-4452`) ia +contul direct de pe lotul de stoc: +``` +SELECT 0 AS ALES, A.ID_GESTIUNE, A.CANTITATE, A.CONT, ... +FROM (SELECT ... A2.CONT, ... FROM STOC A2 ...) A +LEFT JOIN NOM_GESTIUNI B ON A.ID_GESTIUNE = B.ID_GESTIUNE +``` +— `A.CONT` = `STOC.CONT`, fara nicio alta sursa, fara fallback (daca lotul de stoc are `CONT` +`NULL`, ramane `NULL`). + +Cand articolul e gestionabil dar **nu exista stoc** (optiunea `RF_FACTURARE_FARA_STOC = 1`), +`cursor_gestiuni_articol_stoc0` (`ff_...:4459-4558`) foloseste un lant `NVL2` explicit +(`ff_...:4472-4473`): +```sql +CAST(NVL2(B.CONT, B.CONT, NVL2(A.CONT, A.CONT, '371')) AS VARCHAR2(4)) AS CONT, +``` +unde `B` = `NOM_GESTIUNI` (contul implicit al gestiunii) si `A.CONT` = ultimul cont vazut in +`STOC` pentru articol (an curent/precedent). Ordinea reala: **cont gestiune -> ultimul cont din +stoc -> `'371'` hardcodat**. Acesta e singurul loc din pachet cu un `COALESCE`/`NVL2` in cascada +pe `CONT`, si singurul cu un default hardcodat. + +`adauga_articol_factura` (varianta apelata din `frm_facturare_articole.do_scrie_articole`, params +la `ff_...:4998-5024`) **nu recalculeaza** contul — il primeste ca parametru `V_CONT` si il scrie +ca atare (`V_CONT2 := V_CONT` daca `V_CONT <> 'XXXX'`, altfel ramane `NULL` — `ff_...:5044-5046`, +`V_CONT2` la INSERT `ff_...:5277`). `'XXXX'` e sentinela VFP pentru "fara valoare" — vezi +`COMUN\clase\ofacturare.vc2:6963,7123,7992` (`poDateGestiuneDest.Cont = Nvl(loCauta.Cont,[XXXX])`). +Aceeasi logica de trecere directa, fara recalcul, e in `adauga_articol_factura_deviz` +(`ff_...:4675-4745`, `V_CONT` scris direct in `CONT`, fara sentinela `'XXXX'`, fara `NVL`). + +## 3. Precedent ROAAUTO — "Alte servicii" (articol din nomenclator brut, fara politica de pret) + +Cursorul `lcCursorDeviz` (`ROAAUTO\Programe\oproceduri_devize.prg:880-887`) **nu are coloana +`Cont`**. `crsvanztemp` are `Cont c(4)` (`:1190-1192`) dar `INSERT INTO crsvanztemp(...)` de la +`:1201-1206` **nu o include** in lista de coloane si nici in `SELECT`-ul sursa — ramane la +valoarea implicita de camp caracter needatat, adica blank. La apel: +``` +['] + Alltrim(Nvl(poArticol.Cont,'')) + [',] + ... -- oproceduri_devize.prg:1256 +``` +trimite un literal `''` (gol) catre `adauga_articol_factura_deviz(..., V_CONT IN NUMBER, ...)` +(`ff_...:4690`). Un literal `''` convertit implicit la `NUMBER` devine `NULL` in Oracle; `V_CONT` +ajunge `NULL` si e scris direct in `VANZARI_DETALII_TEMP.CONT` (`ff_...:4735`), fara `NVL`, fara +fallback. **Concluzie**: liniile "Alte servicii" din ROAAUTO (articol real, fara politica de pret, +pret tastat manual — vezi `roaauto_articole_lista_preturi.md`) ajung cu `CONT = NULL` in Oracle. +Nu exista in cod niciun raspuns explicit "cont pentru articol fara politica" — rezultatul e pur si +simplu absenta valorii, nu o valoare calculata. + +## 4. Gestionabile vs. negestionabile + +Difera **calea**, nu neaparat sursa initiala: +- **Gestionabil** (`poArticol.gestionabil <> 0`): `frm_facturare_articole.do_adauga_articol` + (`COMUN\clase\ofacturare.vc2:12871-12896`) cere alegerea unui lot/gestiune prin `do_alege_stoc`, + care suprascrie `poArticol.Cont` cu valoarea din `cursor_gestiuni_articol[_stoc0]` (punctul 2) — + deci `STOC.CONT`/`NOM_GESTIUNI.CONT`, nu `NOM_ARTICOLE.CONT`. +- **Negestionabil** (`poArticol.gestionabil = 0`) sau `gnScadereStoc = 0`: se instantiaza + `frm_articol_factura` direct (`:12871-12880`), fara trecere prin `do_alege_stoc` — `poArticol.Cont` + ramane cel citit initial la cautarea articolului, adica `NOM_ARTICOLE.CONT` (punctul 1, sursa 1), + neschimbat. + +## 5. Ce se intampla daca nu se gaseste niciun cont + +- Nicio exceptie/`RAISE_APPLICATION_ERROR` legata de `CONT` in tot pachetul (spre deosebire de + cota de TVA, unde lipsa produce explicit `FACT-012`/`FACT-013`/`FACT-018`, + `ff_...:5151-5153,5184-5187,5203-5206`). +- **Gestionabil, cu stoc**: `CONT` = `STOC.CONT`; daca acesta e `NULL` in stoc, ramane `NULL` — + fara fallback (punctul 2, `cursor_gestiuni_articol`). +- **Gestionabil, fara stoc** (`RF_FACTURARE_FARA_STOC=1`): fallback pana la `'371'` hardcodat + (punctul 2, `cursor_gestiuni_articol_stoc0`). +- **Negestionabil / articol adaugat fara trecere prin gestiune** (inclusiv "Alte servicii" ROAAUTO): + `CONT` ramane ce a venit din `NOM_ARTICOLE.CONT`; daca e gol, `Nvl(poArt.Cont,'')` -> `''` -> + `NULL` in Oracle (`adauga_articol_factura`, sentinela `'XXXX'` la `ff_...:5044-5046`) — linie + scrisa **fara cont**, fara eroare. + +## Neverificat + +- Unde (daca undeva) se determina un **cont de venit propriu-zis** (707/704/706/708) pentru nota + contabila a vanzarii — nu e in `pack_facturare`; grep pentru `707`/`704`/`706`/`708` in + `ff_2026_08_06_10_COMUN_PACK_FACTURARE.sql` si in `ROAFACTURARE\Programe`/`COMUN\programe\ofacturare_comun.prg` + nu a gasit nimic. Probabil intr-un pachet de contabilitate/note contabile separat, neinclus in + scriptul analizat — ar necesita identificarea acelui pachet (posibil in ROACONT sau un + `pack_contabilitate`/`pack_note_ct`) si urmarirea generarii notei contabile din `VANZARI`/`VANZARI_DETALII`. +- Continutul exact al coloanei `NOM_ARTICOLE.CONT` pentru articole de tip "serviciu" (daca e + populata cu conturi de cheltuiala/productie 6xx/3xx sau lasata goala) — ar necesita o interogare + pe schema Oracle live, in afara bugetului acestei cercetari (doar cod static disponibil). +- Rolul exact al `ID_VENCHELT`/`NOM_VENIT_CHELTUIELI` (parametru la nivel de `ACT`/factura, vazut + la `ff_...:191,1885` si in `initializeaza_date_factura`) — pare o clasificare + venituri/cheltuieli la nivel de document, nu un cont de venit per linie; nu am urmarit + consumatorul lui pana la capat (posibil in raportare, nu in inregistrarea contabila). diff --git a/docs/cercetare/cont_venit_corespondente.md b/docs/cercetare/cont_venit_corespondente.md new file mode 100644 index 0000000..0b3e7ec --- /dev/null +++ b/docs/cercetare/cont_venit_corespondente.md @@ -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`. diff --git a/docs/cercetare/coresp_cont_venchelt.md b/docs/cercetare/coresp_cont_venchelt.md new file mode 100644 index 0000000..52be8b8 --- /dev/null +++ b/docs/cercetare/coresp_cont_venchelt.md @@ -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 `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. diff --git a/docs/cercetare/custodie_48_49_stergere_reemitere.md b/docs/cercetare/custodie_48_49_stergere_reemitere.md new file mode 100644 index 0000000..1c10088 --- /dev/null +++ b/docs/cercetare/custodie_48_49_stergere_reemitere.md @@ -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. diff --git a/docs/cercetare/discount_document_cota_tva.md b/docs/cercetare/discount_document_cota_tva.md new file mode 100644 index 0000000..586eff4 --- /dev/null +++ b/docs/cercetare/discount_document_cota_tva.md @@ -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 % 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 % "`, 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 + + ... Discount + 539.82 + Z0... + +0.00 + -539.82 + 0.00 + Z0... + 4410.00 + 0.00 + E0 + Scutit cu drept de deducere... + +``` + +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 +false + 95 + Discount + 539.82 + E0 + VAT +``` + +**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. + diff --git a/docs/cercetare/discount_document_cota_tva_b.md b/docs/cercetare/discount_document_cota_tva_b.md new file mode 100644 index 0000000..10dece3 --- /dev/null +++ b/docs/cercetare/discount_document_cota_tva_b.md @@ -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 + + ... Discount + 539.82 + Z0... + +0.00 + -539.82 + 0.00 + Z0... + 4410.00 + 0.00 + E0 + Scutit cu drept de deducere... + +``` + +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 -v FACT1 `, 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 + ... 539.82 +539.82 +``` + +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. diff --git a/docs/cercetare/discount_in_rapoarte_si_efactura.md b/docs/cercetare/discount_in_rapoarte_si_efactura.md new file mode 100644 index 0000000..dd63570 --- /dev/null +++ b/docs/cercetare/discount_in_rapoarte_si_efactura.md @@ -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: +1014: +1026: +1062: -- 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. diff --git a/docs/cercetare/discount_pe_articol.md b/docs/cercetare/discount_pe_articol.md new file mode 100644 index 0000000..e3e7b8c --- /dev/null +++ b/docs/cercetare/discount_pe_articol.md @@ -0,0 +1,164 @@ +# Investigatie: discount pe articol (linie de factura) — ROAFACTURARE + +Intrebare: discountul pe articol e azi doar procentual, sau exista si discount in valoare +absoluta pe unitate / pe linie? + +## 1. Model de date — DISCOUNT / DISCOUNT_UNITAR + +**Concluzie**: Exista deja o coloana de discount **in valoare absoluta pe unitate** +(`DISCOUNT_UNITAR`) pe `VANZARI_DETALII` (si pe `VANZARI_DETALII_TEMP`, tabela de lucru din care +se compune factura), pe langa discountul de document `VANZARI.DISCOUNT` (valoare absoluta +rezultata dintr-un procent aplicat la baza, nu procent stocat). Exista si un flag boolean +`VANZARI.DISCOUNT_EVIDENTIAT`. + +**Dovezi**: +- Tip coloana: `alter table VANZARI_DETALII modify discount_unitar NUMBER(22,6);` / + `alter table VANZARI_DETALII_TEMP modify discount_unitar NUMBER(22,6);` / + `alter table CRM_POLITICI_PRET_ART modify discount_unitar NUMBER(22,6);` — + `D:\ROA\DATABASE\SCRIPTURI_CLAR\2024\06\ff_2024_06_13_02_COMUN_FACTURARE.sql:3,9,16`. Precizia + `(22,6)` e cea folosita la coloane de pret/valoare, nu la procente. +- Exista si la nivel de politica de pret (`CRM_POLITICI_PRET_ART.DISCOUNT_UNITAR`), deci + discountul unitar poate proveni din politica de pret a clientului, nu doar tastat pe linie. +- Coloana e folosita direct ca scadere din pret: `A.PRET - NVL(A.DISCOUNT_UNITAR, 0) AS PRET` — + `D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql:3315,3500`. +- Document-level: `v.discount as disc_fara_tva` / `v.discount_evidentiat` — + `D:\ROA\DATABASE\SCRIPTURI_CLAR\2011\08\ff_2011_08_30_02_FACTURARE.sql:36-42,251-252,267,291`. + Discountul document e o valoare (lei/valuta), calculata din procent tastat de operator x baza + (vezi punctul 3), nu un procent stocat pe `VANZARI`. + +## 2. VFP — grid-uri de linii factura (`frm_facturare_articole` si `frm_facturare_articole2`) + +**Concluzie**: Ambele forme au coloane de grid pentru discountul unitar **in valoare absoluta**, +cu etichete explicite "Discount unitar cu TVA" / "Discount unitar in valuta fara TVA". In +`frm_facturare_articole` (forma standard) aceste coloane sunt read-only in grid (afisare, nu +editare directa pe linie); in `frm_facturare_articole2` (forma "noua", optionala) sunt editabile +si exista in plus o coloana de **procent pe linie** ("Procent discount", legata de campul +`procdisc`). + +**Dovezi**: +- `frm_facturare_articole`: `Column5.ControlSource = "discountctva"`, `Column5.Name = + "cDiscountCTva"`, `Column5.ReadOnly = .T.` si `Column9.ControlSource = "vdiscountftva"`, + `Column9.Name = "cVdiscountftva"` (fara `ReadOnly`, deci editabil implicit) — + `D:\ROA\ROAFACTURARE\COMUN\clase\ofacturare.vc2:12311-12317, 12340-12345`. Header-e: + `Caption = "Discount unitar cu TVA"` (`:12409`), `Caption = "Discount unitar in valuta fara + TVA"` (`:12546`). +- `frm_facturare_articole2`: `Column5.Name = "cDiscountCTva", Column5.ReadOnly = .F.` + (`:16656-16657`), `Column9.Name = "cVdiscountftva", Column9.ReadOnly = .F.` (`:16687-16688`), + plus `Column14.ControlSource = "procdisc"`, `Column14.Name = "cProcentDiscount"`, + `Column14.Format = "RK"`, `Column14.InputMask = "99 999.99"`, `Column14.ReadOnly = .F.` + (`:16720-16727`), header `Caption = "Procent discount"` (`:16934-16940`). +- Vizibilitate conditionata: nu de `poDate.tip` direct, ci de `poDate.in_valuta` — + `If poDate.in_valuta = 0 ... RemoveObject([cVdiscountftva]) ... Else ... + RemoveObject([cDiscountctva])` — `ofacturare.vc2:15269-15278` (pattern analog si la `:19065` + pentru `frm_facturare_articole2`). +- `frm_facturare_articole2` e optionala, activata prin `gnFacturareNou = 1` cu un dialog Da/Nu + ("Facturare noua (DA) sau standard (NU)?") — `COMUN\programe\ofacturare.prg:87-93`, instantiata + la `:929` (`lcObject = [frm_facturare_articole2]`). + +## 3. Calculul + `discount_evidentiat` + +**Concluzie**: `DISCOUNT_UNITAR` se scade direct din pretul unitar **fara TVA**, in valuta +documentului, inainte de a se aplica TVA-ul, apoi se inmulteste cu cantitatea. Flagul +`DISCOUNT_EVIDENTIAT` schimba doar mecanismul aritmetic (scade discountul din valoarea deja +calculata vs. din pretul unitar rotunjit), nu semantica — discountul ramane o valoare absoluta pe +unitate in ambele ramuri. In VFP, checkbox-ul corespunzator e etichetat "Se pune in evidenta +discount-ul pe articole in notele contabile si pe factura" — adica discountul e afisat separat pe +document/nota contabila vs. absorbit tacit in pret. + +**Dovezi** (`FUNCTION calculeaza_total_fara_tva_fact`, +`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql:15794-15847`): + +``` +IF V_DISCOUNT_EVIDENTIAT = 1 THEN + V_SUMA_FARA_TVA := ROUND((ROUND(curs*PRET,...) - DIFERENTA)*CANTITATE,...) + - ROUND(ROUND(curs*NVL(DISCOUNT_UNITAR,0),...)*CANTITATE,...) +ELSE + V_SUMA_FARA_TVA := ROUND((ROUND(curs*ROUND(PRET,...),...) + - ROUND(curs*ROUND(NVL(DISCOUNT_UNITAR,0),...),...) - DIFERENTA)*CANTITATE,...) +``` + +Aceeasi structura simetrica in `calculeaza_total_tva_fact` (`:15899-15944`). In VFP, sinteza +corespunzatoare: `valdiminuatftva WITH poArticol.pretftva - NVL(poArticol.discount_unitar,0)` — +`ofacturare.vc2:13126`, conversia cu-TVA: `discount_unitar_ctva = +discount_unitar*(proc_tvav-1) + discount_unitar` — `:13635-13636`. Checkbox: +`ADD OBJECT 'ck_discountevidentiat' AS _checkbox WITH ... Caption = "Se pune in evidenta +discount-ul pe articole in notele contabile si pe factura", ControlSource = +"poDate.discount_evidentiat"` — `:11300-11305`. + +## 4. Oracle — parametrii `adauga_articol_factura` + +**Concluzie**: procedura primeste discountul ca parametru numeric absolut +`V_DISCOUNT_UNITAR IN NUMBER` si il scrie direct in `VANZARI_DETALII_TEMP.DISCOUNT_UNITAR`, fara +nicio transformare procent->valoare. Discountul de document (`VANZARI.discount`) e separat, +aplicat/gestionat la alt nivel (agregare pe factura, cf. `do_calculeaza_discount` in VFP). + +**Dovezi**: semnatura completa +`PROCEDURE adauga_articol_factura(V_ID_TEMP IN NUMBER, ... V_CANTITATE IN NUMBER, +V_DISCOUNT_UNITAR IN NUMBER, V_CONT IN VARCHAR2, ...)` — +`D:\ROA\DATABASE\SCRIPTURI_CLAR\2026\03\ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql:4972-4998`, +parametrul `V_DISCOUNT_UNITAR` la linia `4986`; +`INSERT INTO VANZARI_DETALII_TEMP (... DISCOUNT_UNITAR ...) VALUES (... V_DISCOUNT_UNITAR ...)` — +`:5219, 5249`. + +## 5. Discount unitar in valoare absoluta — exista deja in suita + +**Concluzie**: Da, exista deja in toata suita ROA — nu doar "conceptual", ci implementat si +cablat de la Oracle pana la grid. `COMUN\clase\ofacturare.vc2` (fisierul verificat pentru +ROAFACTURARE) e literalmente acelasi fisier partajat cu +`D:\ROA\ROAGEST\COMUN\clase\ofacturare.vc2` si `D:\ROA\ROAACNPRO\COMUN\clase\ofacturare.vc2` +(confirmat prin grep pe ambele arbore), la fel `pack_facturare`. Nu a fost nevoie de o cautare +separata in alt produs — e literalmente acelasi cod, aceeasi coloana. + +**Dovezi**: `discount_unitar`/`discountunitar` apare identic in +`D:\ROA\ROAGEST\COMUN\clase\ofacturare.vc2`, `D:\ROA\ROAGEST\COMUN\clase\ofacturare_comun.vc2`, +`D:\ROA\ROAGEST\COMUN\programe\ofacturare*.prg`, `D:\ROA\ROAACNPRO\COMUN\clase\ofacturare.vc2` si +`D:\ROA\ROAACNPRO\COMUN\clase\ofacturare_comun.vc2`; SQL-uri de discount identice (ex. +`v.discount as disc_fara_tva`) gasite si in +`D:\ROA\DATABASE\SCRIPTURI_CLAR\2021\02\ff_2021_02_03_01_VANZARI.sql:19-20,259-260,409-410, +481-482,612`. + +## 6. Ce ar presupune un discount unitar in valoare absoluta — inventar (nu solutie, doar puncte de atins) + +- **Coloana tabela**: deja exista (`VANZARI_DETALII.DISCOUNT_UNITAR`, `NUMBER(22,6)`, plus pe + `VANZARI_DETALII_TEMP` si `CRM_POLITICI_PRET_ART`) — nimic de adaugat. +- **Parametru Oracle**: deja exista (`adauga_articol_factura(... V_DISCOUNT_UNITAR ...)`, + `ff_2026_03_23_04_COMUN_PACK_FACTURARE.sql:4986`) — nimic de adaugat. +- **Coloana grid**: deja exista in ambele forme (`cDiscountCTva`/`cVdiscountftva`); in + `frm_facturare_articole` (forma standard, majoritara) e read-only — de decis daca se face + editabila pe linie sau ramane doar afisaj derivat din politica de pret / discountul procentual + de pe factura. +- **Intrare pentru operator**: in forma standard nu s-a gasit un punct unde operatorul tasteaza + manual `discount_unitar` per linie la adaugarea articolului — pare alimentat din + `CRM_POLITICI_PRET_ART.DISCOUNT_UNITAR` (politica de pret a clientului) sau din redistribuirea + discountului procentual de pe factura (`do_calculeaza_discount`, `ofacturare.vc2:13405-13424`). + De clarificat fluxul exact inainte de a proiecta un input nou. +- **Recalcul**: formulele Oracle (`calculeaza_total_fara_tva_fact`/`calculeaza_total_tva_fact`) + deja trateaza `DISCOUNT_UNITAR` ca valoare absoluta — nimic de schimbat aritmetic daca se + reutilizeaza calea existenta. +- **Rapoarte `.frx`**: nu s-a verificat daca discountul unitar apare pe rapoartele tiparite de + factura — necunoscut, de verificat separat. +- **eFactura / SAF-T**: nu s-a verificat daca `discount_unitar` e mapat in XML-ul UBL/eFactura sau + in declaratia SAF-T — necunoscut, de verificat separat (fisierele `COMUN_EFACTURA*.sql` / + `COMUN_SAFT*.sql` contin "discount" dar nu au fost citite). + +## Raspuns scurt la intrebare + +Nu doar procentual: **discountul pe articol exista deja si ca valoare absoluta pe unitate** +(`DISCOUNT_UNITAR`, `NUMBER(22,6)`), implementat capat la capat — coloana Oracle pe +`VANZARI_DETALII`/`VANZARI_DETALII_TEMP`, parametru in `pack_facturare.adauga_articol_factura`, +formule de calcul care scad direct din pret, si coloane de grid in VFP cu eticheta explicita +"Discount unitar cu TVA"/"...in valuta fara TVA". Discountul de document (`VANZARI.discount`) +ramane procentual-la-origine (tastat ca procent, stocat ca valoare calculata). Ce nu e clar e daca +operatorul poate tasta liber discountul unitar pe linie in forma standard +(`frm_facturare_articole`, unde coloana e read-only) — in forma "noua" optionala +(`frm_facturare_articole2`, `gnFacturareNou=1`) e editabila si are si un procent-pe-linie separat. + +## Necunoscute ramase + +- Sursa exacta a valorii `discount_unitar` la adaugarea unui articol in forma standard (politica + de pret vs. redistribuire din discountul procentual de pe factura) — nu s-a urmarit tot lantul + `do_adauga`/`crsgestarticol`. +- Daca `discount_unitar` apare pe rapoartele `.frx` tiparite. +- Daca `discount_unitar` e transmis in eFactura (UBL) sau SAF-T. +- Cat de folosita e efectiv `frm_facturare_articole2` in productie (e optionala, per sesiune, cu + prompt). diff --git a/docs/cercetare/discount_verificare2.md b/docs/cercetare/discount_verificare2.md new file mode 100644 index 0000000..eb19a09 --- /dev/null +++ b/docs/cercetare/discount_verificare2.md @@ -0,0 +1,182 @@ +# Verificare discount pe linie de factura — frm_facturare_articole / VANZARI_DETALII + +Investigatie read-only. Toate liniile citate au fost verificate pe fisierul text real din working +copy (`ofacturare.vc2`, 23087 linii, ultima scriere 07.08 22:18) si pe scripturile DDL din +`D:\ROA\DATABASE\SCRIPTURI_CLAR`. Nu s-a executat nimic pe baza de date. + +## 1. Confirmare/infirmare afirmatii initiale + +**Concluzie**: toate afirmatiile despre `frm_facturare_articole` sunt corecte, cu linii care se +potrivesc exact sau aproape exact. Confirmat via `vfp_symbols.ps1 -Where` ca formularul +`frm_facturare_articole` ocupa exact `ofacturare.vc2:10968-15739`. + +**Dovezi**: +- `Column5.ControlSource = "discountctva"`, `Column5.Name = "cDiscountCTva"`, `Column5.ReadOnly = .T.` + — confirmat la `ofacturare.vc2:12311-12317` (identic cu afirmatia). +- `Column9.ControlSource = "vdiscountftva"`, `Column9.Name = "cVdiscountftva"`, **fara** `ReadOnly` + — confirmat la `ofacturare.vc2:12340-12345` (identic). +- Capete de coloana: `Caption = "Discount unitar cu TVA"` la `:12409`, `Caption = "Discount unitar in + valuta fara TVA"` la `:12546` — ambele confirmate exact. +- Excludere reciproca pe valuta la `ofacturare.vc2:15269-15278`: + ``` + 15269 If poDate.in_valuta = 0 + 15270 Thisform.grd_factura.RemoveObject([cVpretFtva]) + 15271 Thisform.grd_factura.RemoveObject([cVdiscountftva]) + 15272 Thisform.grd_factura.RemoveObject([cVvaldiminuatftva]) + 15273 Else + 15274 Thisform.grd_factura.RemoveObject([cPretFtva]) + 15275 Thisform.grd_factura.RemoveObject([cDiscountctva]) + ``` + confirmat identic (linii exacte, nu doar apropiate). +- `VANZARI_DETALII.DISCOUNT_UNITAR NUMBER(22,6)` — confirmat, dar cu precizare: linia corecta in + `ff_2024_06_13_02_COMUN_FACTURARE.sql` e `:3` (nu si `:9`/`:16` cum lasa sa se inteleaga + formularea initiala — acelea sunt `VANZARI_DETALII_TEMP` si, respectiv, `CRM_POLITICI_PRET_ART`, + vezi punctul 4). + +## 2. Consecinta practica pe ecran + +**Concluzie**: pe factura in lei, coloana vizibila e **"Discount unitar cu TVA"** (`discountctva`) +si e **needitabila** in grid (`ReadOnly = .T.`). Pe factura in valuta, coloana vizibila e +**"Discount unitar in valuta fara TVA"** (`vdiscountftva`) si **este editabila direct in grid** +(nicio proprietate `ReadOnly` setata pe coloana sau pe `Text1`-ul ei → mosteneste default-ul VFP, +`.F.`). + +**Dovezi**: +- Ramane pe ecran: cand `in_valuta = 0` se elimina cele 3 coloane "V..." (inclusiv + `cVdiscountftva`) → ramane `cDiscountCTva`. Cand `in_valuta <> 0` se elimina `cDiscountctva` (si + celelalte 3 coloane fara "V") → ramane `cVdiscountftva`. (`:15269-15278`, citat mai sus). +- Ca sa nu presupun ca lipsa lui `Column9.ReadOnly` inseamna implicit editabil, am verificat lantul + de clase: grid-ul `grd_factura` e `ADD OBJECT 'grd_factura' AS _grdrow` (`:12263`), fara + `ReadOnly` la nivel de grid intre `:12263-12364`; clasa `_grdrow` (`_grd_base.vc2:445`) nu + seteaza `ReadOnly` nicaieri in propriul body, iar parintele ei `_grdbase`/`_grid` de asemenea nu + (singurele 3 aparitii de `ReadOnly` in `_grd_base.vc2` sunt in clasa separata `_grdfooter`, + neinrudita). Deci Column9 chiar e editabila. +- Confirmare suplimentara: in formularul **`frm_facturare_articole2`** (prototip separat, clasa la + `ofacturare.vc2:15741-19355`, NU formularul in productie), aceleasi doua coloane apar cu + `ReadOnly` explicit: `Column5.ReadOnly = .F.` (`:16657`) si `Column9.ReadOnly = .F.` (`:16688`) — + adica in prototip discountul e editabil in ambele monede direct din grid; in formularul real + doar varianta in valuta e editabila din grid. + +## 3. Poate operatorul introduce azi un discount pe linie, si pe ce cale + +**Concluzie**: da, poate — dar nu prin `do_calculeaza_discount` de la +`frm_facturare_articole:13405-13424` (asta calculeaza discountul **global pe toata factura**, +`Thisform.ndiscfactron`/`ndiscfactval`, nicio legatura cu `discountctva`/`vdiscountftva` pe +linie — vezi corectia de la final). Calea reala e formularul **`frm_articol_factura`** +(`ofacturare.vc2:1108-2659`), deschis din `do_adauga_articol` la adaugarea unui articol +(`:12873`, `ofrmadarticol.Show(1)` doar daca `!tlImplicit`). Acolo exista metoda +`frm_articol_factura.do_calculeaza_discount` (`:1874-1976`) legata de 3 controale editabile: +`Clb_procent_discount.Text_simplu1` (procent), `Clb_discount_unitar.tx_suma_nat`/`tx_suma_val` +(suma in lei / valuta), `Clb_discountctva.tx_suma_nat`/`tx_suma_val` (varianta cu TVA) — toate cu +handler `Valid`/`InteractiveChange` care apeleaza `do_calculeaza_discount(valoare, tip)` cu +`tip=1` procent, `tip=2` lei, `tip=3` valuta (`:2602-2624`, `:2646-2649`). + +**Lantul, cu linii**: +1. Operator tasteaza in unul din campurile de mai sus → `Valid`/`InteractiveChange` → + `Thisform.do_calculeaza_discount(valoare, tip)` (`ofacturare.vc2:2602-2649`). +2. `do_calculeaza_discount` (`:1874-1976`) scrie in obiectul `poArticol`: + `poArticol.discount_unitar`, `discount_unitar_val`, `discount_unitar_ctva`, + `discount_unitar_ctva_val` (ex. `:1898-1906`, `:1943-1951`). +3. La revenirea in `do_adauga_articol` (`:12813-13086`), `poArticol` e scris in `crsfactura` prin + `Gather`/`Replace`: + ``` + 12956 Replace id_temp With Recno(), codmat With Nvl(poArticol.codmat, Space(50)), ; + ... + 12957 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) + ``` + (linii `12956-12957`; varianta pentru selectie multipla de articole la `13003-13005`). +4. `discount_unitar` initial (inainte de tastare) e preluat din `crsarticole` (cursorul de stoc, + populat inainte de deschiderea dialogului) prin `do_initializeaza_articol` (`:13618` si urm.): + `toArticol.discount_unitar = Nvl(toArticol.discount_unitar, 0)` (`:13630`) — deci daca stocul / + politica de pret vine deja cu un discount populat, acela e valoarea implicita afisata in dialog, + pe care operatorul o poate suprascrie. +5. Cale suplimentara, directa: pe factura in valuta, cum `cVdiscountftva` e editabil in grid + (punctul 2), operatorul poate scrie si direct in celula `vdiscountftva` din `grd_factura` — dar + fara niciun `Valid`/`InteractiveChange` propriu definit pentru acea coloana in + `frm_facturare_articole` (cautat explicit `cVdiscountftva`/`cDiscountCTva` in intervalul + `10968-15739`: singurele hit-uri sunt definitiile de coloana si `RemoveObject`, niciun handler + de eveniment) — editarea directa in grid NU declanseaza recalcularea automata a + `vvaldiminuatftva`/totaluri, doar modifica valoarea bruta in `crsfactura`. + +## 4. Structura reala a coloanelor de discount pe VANZARI_DETALII + +**Concluzie**: nu exista un `CREATE TABLE VANZARI_DETALII` in `SCRIPTURI_CLAR` (arhiva incepe din +2009, iar tabela exista deja atunci — coloana `DISCOUNT_UNITAR` era deja in uz in pachete PL/SQL +din 2009, deci a fost creata inainte de arhiva). Singura coloana de discount pe +`VANZARI_DETALII`/`VANZARI_DETALII_TEMP` gasita in `SCRIPTURI_CLAR` e **`DISCOUNT_UNITAR`**, si +singura modificare de tip/precizie inregistrata e cea din 2024. + +**Dovezi (cronologic, tot ce am gasit cu `DISCOUNT` in contextul acestei tabele)**: +- Nu exista niciun `CREATE TABLE VANZARI_DETALII` sau `ALTER TABLE VANZARI_DETALII ADD DISCOUNT...` + in toata arhiva `SCRIPTURI_CLAR` (2009-2026). Cel mai vechi hit pe `DISCOUNT_UNITAR` legat de + aceasta tabela e o declaratie de variabila `V_DISCOUNT_UNITAR VANZARI_DETALII.DISCOUNT_UNITAR%TYPE` + in pachetul `PACK_FACTURARE`, prezenta deja in scripturile din 2009 (ex. + `2009\9\ff_2009_09_03_01_FACTURARE_PACK_FACTURARE.sql`) — coloana exista deja atunci. +- Singura modificare de precizie gasita: `ff_2024_06_13_02_COMUN_FACTURARE.sql:3` — + `alter table VANZARI_DETALII modify discount_unitar NUMBER(22,6);` — si simetric pentru + `VANZARI_DETALII_TEMP` la linia `:9`, si pentru `CRM_POLITICI_PRET_ART` (tabela de **politici de + pret**) la linia `:16`. Deci forma finala confirmata: **`DISCOUNT_UNITAR NUMBER(22,6)`**, o + singura coloana de discount (valoare, nu procent), identica pe toate cele 3 tabele. +- Nu exista alte coloane `DISCOUNT%`/`VDISCOUNT%` la nivel Oracle pe `VANZARI_DETALII` — cele patru + campuri VFP `discountftva`/`discountctva`/`vdiscountftva`/`vdiscountctva` din `crsfactura` NU au + corespondent 1:1 in schema Oracle; la scriere (`Gather`/`Replace` in `do_adauga_articol`, punctul + 3) doar `discount_unitar` (si varianta cu TVA calculata din el) ajunge, prin campurile + intermediare, la coloana unica Oracle. +- Convenția de interogare a bazei de dezvoltare, gasita in `COMUN\docs\scripturi-migrare-db.md:36-40`: + sursa de referinta pentru DDL e schema de dezvoltare **`MARIUSM_AUTO`** (bazata pe + `ROA_CENTRAL`), interogata prin `goExecutor`, niciodata o schema de client. Nu am rulat nimic pe + baza — doar raportez conventia, asa cum a cerut sarcina. + +## 5. Coloana de procent de discount pe VANZARI_DETALII / campul procdisc + +**Concluzie**: **nu** exista o coloana de procent de discount pe `VANZARI_DETALII` (doar +`DISCOUNT_UNITAR`, o valoare absoluta). Campul `procdisc` din `frm_facturare_articole2` e doar un +camp de lucru in grid, **fara nicio scriere in cursorul local si fara corespondent Oracle** — +practic un camp scaffolded si neconectat. + +**Dovezi**: +- Cautare `DISCOUNT`+`PROCENT`/`PROC` in `ff_2024_06_13_02_COMUN_FACTURARE.sql` (scriptul care + fixeaza forma finala a coloanelor de discount): nicio potrivire — confirma ca nu exista un + "procent discount" ca si coloana pe `VANZARI_DETALII`. +- `procdisc` apare **o singura data** in tot `ofacturare.vc2`: `Column14.ControlSource = "procdisc"` + la `:16721`, in interiorul clasei `frm_facturare_articole2` (`15741-19355` — prototip, nu + formularul in productie). +- Nicio instructiune `Gather`/`Replace ... procdisc With ...` in tot fisierul (cautat pe tot + `ofacturare.vc2`) — camp fara sursa de populare in cod. +- Toate celelalte aparitii de "procdisc" din fisier sunt de fapt variabila de memorie + `gnMemProcDisc` ("memorare procent discount" — o optiune de aplicatie care controleaza daca + procentul de discount tastat se retine intre articole, `:2299`, `:2441`, `:12835` etc.), fara + legatura cu campul de cursor `procdisc`. +- Cautare `PROCDISC` in `SCRIPTURI_CLAR`: doar 2 hit-uri, ambele in scripturi din 2015 pentru + tabela de **optiuni firma** (`co_2015_12_09_01_OPTIUNI.sql`, `ff_2015_12_09_01_OPTIUNI.sql`) — + legate de aceeasi optiune `gnMemProcDisc`, nu de `VANZARI_DETALII`. + +## Ce era gresit in afirmatiile de mai sus + +- Formularea "linia 3, 9, 16" pentru coloana Oracle `DISCOUNT_UNITAR` grupa laolalta 3 tabele + diferite (`VANZARI_DETALII` la `:3`, `VANZARI_DETALII_TEMP` la `:9`, `CRM_POLITICI_PRET_ART` la + `:16`) ca si cum ar fi acelasi lucru repetat de 3 ori — corect ca valoare (toate devin + `NUMBER(22,6)`), dar sunt 3 tabele distincte, nu 3 confirmari ale aceleiasi coloane. +- Cea mai importanta corectie: linia indicata pentru "de unde se scrie discountctva/vdiscountftva + — din `do_calculeaza_discount` (`ofacturare.vc2:13405-13424`)" trimite la metoda gresita. + `frm_facturare_articole.do_calculeaza_discount` de la acea linie calculeaza discountul **global + pe factura** (`Thisform.ndiscfactron`/`ndiscfactval`), nu discountul pe linie. Metoda care chiar + calculeaza `discount_unitar`/`discount_unitar_ctva` (sursa reala pentru `discountctva` si + `vdiscountftva`) e **`frm_articol_factura.do_calculeaza_discount`**, o metoda cu acelasi nume dar + in alta clasa, la `ofacturare.vc2:1874-1976`. Cele doua metode au nume identic dar apartin la + doua clase diferite din acelasi fisier — o capcana reala de grep fara indexul de simboluri. + +## Necunoscute ramase + +- Nu am verificat daca `crsarticole` (cursorul de stoc din care pleaca `poArticol.discount_unitar` + la `do_initializeaza_articol:13630`) e populat vreodata cu un discount nenul direct dintr-o + interogare de politica de pret (`CRM_POLITICI_PRET_ART`) inainte de a ajunge in dialogul + `frm_articol_factura` — am gasit doar ca acea tabela are aceeasi coloana `discount_unitar` + (`NUMBER(22,6)`), nu am urmarit interogarea SQL efectiva care umple `crsarticole`/`crsartselectate` + (cod probabil in `Programe/`, in afara `ofacturare.vc2`, netrasat din lipsa de timp alocat). +- Nu am confirmat pe date reale (Oracle) daca exista astazi randuri cu `DISCOUNT_UNITAR` populat pe + `VANZARI_DETALII` provenind din editarea directa in grid pe factura in valuta (punctul 2/3, + ultimul paragraf) — doar am aratat ca acea cale exista in cod, fara handler de recalcul. +- Nu am cautat daca `frm_facturare_articole2` (prototipul) e instantiat undeva in productie sau e + cu adevarat mort/nefolosit — task-ul l-a framat deja ca "prototip" si am pastrat presupunerea. diff --git a/docs/cercetare/factura_retur_document.md b/docs/cercetare/factura_retur_document.md new file mode 100644 index 0000000..1d5003b --- /dev/null +++ b/docs/cercetare/factura_retur_document.md @@ -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 "\ 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. diff --git a/docs/cercetare/ff_view_articole_vanzare.sql b/docs/cercetare/ff_view_articole_vanzare.sql new file mode 100644 index 0000000..abf328c --- /dev/null +++ b/docs/cercetare/ff_view_articole_vanzare.sql @@ -0,0 +1,35 @@ +-- 08.08.2026 Marius Mutu +-- VVANZARI_ARTICOLE: liniile unei vanzari (VANZARI_DETALII) cu denumirea articolului, a gestiunii +-- si a valutei, pentru gridul de articole din formularul de modificare a notei. Valori brute, fara +-- conversie valutara si fara calcule - editarea scrie inapoi exact ce a citit. Filtrul STERS ramane +-- pe seama apelantului, ca la VACT_TOT / VRUL_TOT / VRUL_OBINV_TOT. + +CREATE OR REPLACE VIEW VVANZARI_ARTICOLE AS +select vd.id_vanzare, + vd.id_vanzare_det, + vd.id_articol, + vd.cantitate, + vd.pret, + vd.pret_cu_tva, + vd.proc_tvav, + vd.discount_unitar, + vd.id_gestiune, + vd.cont, + vd.id_valuta, + vd.id_jtva_coloana, + vd.serie, + vd.explicatie, + vd.taxcode, + vd.lot, + vd.sters, + na.denumire, + na.codmat, + ng.nume_gestiune, + nv.nume_val + from vanzari_detalii vd + left join nom_articole na on na.id_articol = vd.id_articol + left join nom_gestiuni ng on ng.id_gestiune = vd.id_gestiune + left join nom_valute nv on nv.id_valuta = vd.id_valuta; + +exec pack_migrare.UpdateVersiune('ff_2026_08_08_01_COMUN_VVANZARI_ARTICOLE'); +commit; diff --git a/docs/cercetare/garda_aviz_facturat.md b/docs/cercetare/garda_aviz_facturat.md new file mode 100644 index 0000000..569b65f --- /dev/null +++ b/docs/cercetare/garda_aviz_facturat.md @@ -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:`); +- 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:`). **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. diff --git a/docs/cercetare/gol_ntip4_factura_din_avize.md b/docs/cercetare/gol_ntip4_factura_din_avize.md new file mode 100644 index 0000000..c59822b --- /dev/null +++ b/docs/cercetare/gol_ntip4_factura_din_avize.md @@ -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. diff --git a/docs/cercetare/handoff_propr_custom.md b/docs/cercetare/handoff_propr_custom.md new file mode 100644 index 0000000..c9a50ce --- /dev/null +++ b/docs/cercetare/handoff_propr_custom.md @@ -0,0 +1,103 @@ +# Predare — deblocarea proprietatilor custom pe frm_modific2024 + +Bloc de lucru TERMINAT CU SUCCES (nu la prag de context — predare la incheierea blocului, +conform Regula zero). + +## Concluzie + +**Ipoteza principala ("FoxBin2Prg Prg2Bin nu poate crea proprietati noi intr-un .vcx") e +INFIRMATA**, cu dovada directa (test izolat, mai jos). Cauza reala era alta, si s-a reparat. + +## Cauza reala, cu dovada + +Proprietatile noi (`larearticolevanzari`, `nidvanzare`, `ntipvanzare`) fusesera adaugate de +agentul anterior **doar** in blocul `*` (valorile), dar **lipseau din +`*`** (lista `*p:` care inregistreaza o proprietate custom ca membru real +al clasei). Fara intrarea `*p:`, `Prg2Bin` scrie linia de valoare, dar VFP n-o materializeaza ca +proprietate pe obiect — de-asta `PEMSTATUS` intorcea `.F.` desi valoarea aparea in text si +supravietuia fidelity-check-ului (fidelity-ul verifica doar ca textul regenerat == textul editat, +nu ca proprietatea exista pe obiect). + +Regula era deja documentata **pentru metode** in `COMUN\docs\flux-editare-vfp-text.md:76-78` +("Metoda noua de clasa cere `*m: nume` in `*`; fara ea, ... o arunca +tacut") — se aplica identic si proprietatilor (`*p:`), doar ca nu era scrisa explicit acolo. De +adaugat separat in acel fisier (nu am facut-o eu, e in afara sarcinii primite). + +## Testul izolat care a transat ipoteza + +Nu am atins `omodificari` pentru test. Am copiat `COMUN\clase\_pf_base.vcx/.vct/.vc2` (clasa +`_pfbase`, fara nicio importanta) intr-un folder complet izolat in scratchpad, am adaugat text-only +o proprietate noua (`ltestpropnoua`) cu intrare `*p:` corecta, write-back cu `txt2vcx.ps1` (proiect +izolat, nu ProjectRoot real), apoi `CREATEOBJECT` + `PEMSTATUS`: + +``` +PEMSTATUS(ltestpropnoua)=DA valoare=.T. +``` + +Confirma ca fluxul text->bin **poate** crea proprietati noi, cand sunt inregistrate corect. + +**Bonus gasit tot in acest test**: FoxBin2Prg foloseste o colatie unde `_` sorteaza **dupa** +literele obisnuite (nu ordine ASCII simpla pe litere mici) — `nidvanzare` < `nid_set` alfabetic +pentru tool, desi `'_' (0x5F) < 'v' (0x76)` in ASCII brut pe litere mici. Confirmat printr-un al +doilea test izolat: am scris `*p: nid_set` inaintea lui `*p: nidvanzare` (ordine ASCII) — fidelity +check a **picat** cu exact acest motiv; textul canonic din `\verify\` a aratat ordinea +corecta, `nidvanzare` inaintea lui `nid_set`. Coincide cu ordinea deja existenta, corecta, din +`*`-ul real al lui `frm_modific2024`. De adaugat in `flux-editare-vfp-text.md` / +`foxbin2prg\CLAUDE.md` ca nota separata (nu am facut-o, in afara sarcinii primite). + +## Ce am schimbat in `omodificari.vc2`/`.vcx`/`.vct` + +`COMUN\clase\omodificari.vc2`, clasa `frm_modific2024` (`:6375-15856`), blocul +`*` — 3 linii `*p:` noi, in ordine alfabetica (colatia tool-ului): +- `*p: larearticolevanzari` — linia 6804 (inainte de `lavertizatexigibilizare`) +- `*p: nidvanzare` — linia 6811 (inainte de `nid_set`) +- `*p: ntipvanzare` — linia 6818 (dupa `ntaxcode`) + +Blocul `*` **nu a fost atins** — valorile erau deja acolo, deja in ordinea corecta, +de la agentul anterior. + +Write-back real cu `txt2vcx.ps1 -AllowComun` — **fidelity check OK**. Backup pastrat: +`COMUN\clase\omodificari.vc2.pre_proprietati_custom.bak` (starea dinainte de aceasta reparatie, +distinct de `.pre_s4runda1.bak` mai vechi). + +**Capcana pe care am picat-o si am reparat-o singura**: prima incercare a folosit +`$lines.IndexOf(text_exact)` ca sa gasesc pozitia liniilor `*p:` tinta — a gasit o potrivire +falsa mult mai devreme in fisier (alta clasa cu proprietati cu nume asemanator), inserand 3 linii +in locul gresit. Am restaurat din backup **inainte** de a rula orice write-back cu starea gresita, +am refacut editarea cu index-uri de linie verificate explicit (assert pe continutul exact al +liniei), si abia apoi am scris pe disc. Fisierul de pe disc e curat, o singura editare corecta. + +## Verificare finala — PASS complet + +`test_page3_articole.prg` sub `watchdog_vfp.ps1 -AutoDismiss`: exit 0, 0 dialoguri. + +``` +cod=1140888: PageCount = 3 (asteptat 3), lAreArticoleVanzari = .T. (asteptat .T.), nIdVanzare = 1050 -> PASS +cod=1125486: PageCount = 2 (asteptat 2), lAreArticoleVanzari = .F. (asteptat .F.) -> PASS +``` + +Toate celelalte asertii din suita (verifica_vanzare_nota x3, verifica_coliziune_cod) tot PASS, +neschimbate. `PEMSTATUS(loForm,'lAreArticoleVanzari',5)` si `PEMSTATUS(loForm,'nIdVanzare',5)` +acum `.T.` (erau `.F.` la predarea anterioara). + +## Starea la predare + +- **Fara commit git/SVN.** `svn status` pe COMUN arata `M` pe `omodificari.vcx`/`.VCT` (write-back + real); `.vc2` e `I` (ignorat de SVN, urmarit doar de `comun.git`). +- `comun.git status` arata `omodificari.vc2` modificat — diff-ul cumuleaza **toata munca + necomisa de azi pe S4 runda 1** (PAGE3, grid, cele 3 proprietati), nu doar reparatia mea; nimic + neasteptat. +- **Fara tranzactii Oracle deschise** — testul de verificare face doar `SELECT`-uri prin + `goExecutor`, zero scriere. +- **Niciun proces `vfp9.exe` ramas viu** (verificat, `Get-Process vfp9` gol). +- Scratch-ul de test izolat (`_pf_base.vcx` copiat) a ramas doar in + `C:\Users\...\scratchpad\testproj\` — in afara working copy, nu necesita curatare. + +## Ce ramane (in afara sarcinii primite azi) + +Pasii 3-5 din `docs\progres.md` sectiunea „#6, S4 runda 1": scoaterea liniilor `[BISECT]`/`[DIAG]` +din test, stergerea `test_baseline_isolation.prg` (concluzia lui e nula, vezi handoff anterior), +scrierea `docs\diff_s4_runda1_page3.patch` + `docs\cercetare\rec_s4_runda1.md`, actualizarea +`COMUN\docs\testare-ui-vfp.md` cu cele trei capcane noi (`DO...WITH` prin referinta, `SET PATH` +ROACONT, watchdog fara input real) **plus** cele doua descoperite acum (`*p:` obligatoriu si +pentru proprietati, colatia cu `_` dupa litere) — raman pentru runda urmatoare. diff --git a/docs/cercetare/handoff_s4_runda1.md b/docs/cercetare/handoff_s4_runda1.md new file mode 100644 index 0000000..9361b47 --- /dev/null +++ b/docs/cercetare/handoff_s4_runda1.md @@ -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 + `*`, 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 + `\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 ` 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 (`docs\diff_s4_runda1_page3.patch`, `git diff --no-index `) 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. diff --git a/docs/cercetare/handoff_test_writeback.md b/docs/cercetare/handoff_test_writeback.md new file mode 100644 index 0000000..3631c29 --- /dev/null +++ b/docs/cercetare/handoff_test_writeback.md @@ -0,0 +1,121 @@ +# Handoff — test real write-back buton=1 (do_editare_factura) + +Predare la prag de context, conform Regula zero din `CLAUDE.md`. Fara analiza noua aici, doar +starea. + +## CORECTIE IMPORTANTA fata de ce stie team-lead acum + +Team-lead a verificat baza INAINTE de ultima rulare si a raportat "`id_vanzare=1048` e neatins, +nimic de curatat". **Nu mai e adevarat** - intre timp corectia `LOCAL` -> `PRIVATE` a fost aplicata +SI rulata, iar testul **a reusit complet, cu COMMIT real, de doua ori**. Documentul `id_vanzare=1048` +**a fost modificat legitim**, exact cum era scopul aprobat de Marius: + +- `cod` a trecut `1140886` -> `1140893` (TEST 1, salvare fara modificari) -> `1140894` (TEST 2, + cu explicatia unui rand `ACT` modificata: "NOTA 1" -> "NOTA 1 (test writeback)"). +- **Cod-ul curent activ in baza pentru acest document este `1140894`**, nu `1140886`. +- Randurile vechi (`cod=1140886` si `cod=1140893`) raman in `ACT`/`RUL` cu `STERS=1` - asta e + comportamentul normal, prin design (vezi "Fapte stabilite" din `docs\progres.md`). +- `VANZARI.total_cu_tva`/`total_fara_tva`/`total_tva`/`id_fact`/`sters` au ramas neschimbate, + verificat si din log VFP si independent prin `sqlplus` dupa rulare. +- `VANZARI_DETALII` a ramas neatins (verificat, cum era de asteptat pe aceasta cale). + +**Raport complet deja scris**: `docs\cercetare\rec_test_writeback.md` (tabel cu cele 5 verificari +pe ambele teste, PASS pe toate, plus istoricul celor 2 incidente si cum s-au rezolvat). Rezultatul +a fost deja trimis catre team-lead prin mesaj ("PASS complet pe ambele teste, commit real +confirmat independent") - posibil incrucisat cu mesajul lui de STOP. + +## 1. Starea fisierului de test + +`COMUN\utile\Teste\editare_factura\test_writeback_buton1.prg` (ultima modificare azi, 08.08.2026). + +- **Corectia `LOCAL` -> `PRIVATE` (linia ~133, acum ~149-153): DA, APLICATA.** Declararea curenta: + ```foxpro + PRIVATE pnAn, pnLuna, lnCod, lnIdFact + LOCAL lnSters, llEProforma + LOCAL lnIdSet, lnIdFactD, lnSucces, llGasitRand + ``` + (restul variabilelor de test raman `LOCAL`, corect - nu sunt folosite ca bind `?` in SQL). +- `frm_modific2024` e ocolit COMPLET (nu se mai instantiaza deloc) - s-a blocat de doua ori + headless (a se vedea sectiunea 3) si s-a decis cu team-lead sa fie scos din harness. `buton=1` + e fortat direct in cod, `inainte_de_do_termin` NU se executa. + `Thisform.do_deschide_tranzactie`/`do_inchide_tranzactie` sunt reproduse inline in fisier + (`MyDeschideTranzactie`/`MyInchideTranzactie`, copiate dupa `_frm_base.vc2:252-302`). +- `PUBLIC gcMockUltimMesaj, gnMockUltimTip` + logare `goExecutor.cEroare` dupa fiecare pas: DA, + adaugate (procedura `LogMockSiEroare`, apelata dupa fiecare `OSCRIE_IN_FISIERE` si dupa + `finalizeaza_modificare_nota`). +- Logare granulara `[chk]` inainte/dupa fiecare sub-pas din ramura `buton=1`: DA, adaugata. +- **Fisierul e in stare FINALA, functionala - nu mai necesita nicio corectie pentru scopul cerut.** + Orice rulare viitoare pe acest script trebuie sa citeasca `cod`-ul curent din `VANZARI` (nu + presupune `1140886`), pentru ca scriptul insusi face asta (interogheaza `VANZARI` la inceputul + fiecarui apel al procedurii `test_editare_writeback`). + +## 2. Comanda exacta de rulare + +```powershell +$testPrg = 'D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_writeback_buton1.prg' +Start-Process 'C:\Program Files (x86)\Microsoft Visual FoxPro 9\vfp9.exe' -ArgumentList '-A','-T',"`"$testPrg`"" -PassThru +``` + +Log: `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\editare_factura\test_writeback_buton1_log.txt` +(suprascris de la zero la fiecare rulare - `STRTOFILE(..., lcLog)` fara `,1` pe prima linie). + +Inainte de orice rulare: verifica sa nu existe deja un `vfp9.exe` activ +(`Get-CimInstance Win32_Process -Filter "Name='vfp9.exe'"`) si sterge `.FXP`/`.ERR`/log vechi din +acelasi folder. + +## 3. Ce s-a stabilit deja (nu se reia) + +- Prima varianta a testului instantia `frm_modific2024` (modeless, `WindowType=0`, ca in + `test_page3_articole.prg`) - s-a blocat de doua ori, headless, fara nicio linie de eroare in + log: prima data pe un dialog nativ Windows **"Open"** (`#32770`, confirmat prin + `EnumWindows`/`GetWindowText` pe procesul viu), a doua oara (dupa ce s-a scos `frm_modific2024` + si inainte de corectia `PRIVATE`) pe un dialog **nativ VFP "View Parameter"** ("Enter the value + for pnLuna"), confirmat de captura de ecran trimisa de Marius si de `EnumWindows` local. +- **Ipoteza "backupset" (clasa `BackupXML`, `oproceduri_comune.prg:3685-3987`) respinsa**: foloseste + doar `CREATE CURSOR`/`Cursortoxml`/`Delete File`, niciun `USE` pe tabela lipsa; si oricum prima + rulare (cea cu `-1`) trecuse deja prin acelasi cod fara sa se blocheze. +- **Cauza reala a dialogului "View Parameter"**: `pnAn`/`pnLuna` erau declarate `LOCAL` in harness, + in loc de `PRIVATE` ca in codul real (`ofacturare_comun.vc2:3742`). `PRIVATE` le face vizibile + in josul stivei de apel, unde `goExecutor.oExecuta` rezolva bind-urile `?pnLuna`/`?pnAn` din + apelul catre `pack_contafin.finalizeaza_modificare_nota`. Cu `LOCAL`, VFP nu le gasea si deschidea + dialogul nativ de introducere manuala - niciodata catchabil prin `ON ERROR`/`TRY`/mock de + `amessagebox` (nu e un `AMESSAGEBOX`, e un mecanism VFP intern). +- **Dupa corectie (`PRIVATE`), ambele teste au trecut curat, cu COMMIT real** - vezi "CORECTIE + IMPORTANTA" de mai sus si `docs\cercetare\rec_test_writeback.md` pentru tabelul complet. +- Inainte de corectie, o rulare intermediara aratase `OSCRIE_IN_FISIERE(2,.T.,.T.) => -1` (esec + curat, cu ROLLBACK, fara nicio scriere) - **acel `-1` nu s-a mai reprodus dupa corectia + `PRIVATE`** (ambele `OSCRIE_IN_FISIERE` au intors `1` in rularea finala). Motivul exact al lui + `-1` din acea rulare intermediara **ramane neexplicat definitiv** (posibil efect secundar al + aceleiasi probleme de scope, posibil altceva) - nu mai e relevant, testul final a trecut, dar + daca reapare vreodata pe alt document, `gcMockUltimMesaj`/`goExecutor.cEroare` sunt deja logate + dupa fiecare pas. + +## 4. Ce e interzis (neschimbat) + +- Nu mock-ui `OSCRIE_IN_FISIERE`. +- Nu modifica codul aplicatiei (`ofacturare_comun.vc2`, `oscrie_in_fisiere.prg`, + `omodificari.vc2` etc.) - niciun bug de aplicatie n-a fost gasit, toate problemele au fost in + harness. +- Nu folosi `cod=1140888` / `cod=1140885` (baze de regresie ale `test_incarca_cursoare.prg`). +- Nu rula `git_sync.ps1` si nu atinge `omodificari.vc2`/`.vcx` (alt agent lucreaza in paralel pe + PAGE3, task separat). + +## 5. Starea datelor - vezi CORECTIA de la inceputul fisierului + +Pe scurt: `id_vanzare=1048` a fost editat legitim de doua ori prin testul aprobat. `cod` curent = +`1140894`. Nimic de reparat sau de facut rollback - e rezultatul dorit al testului. Daca se doreste +un test suplimentar pe alt document, se alege un `cod`/`id_vanzare` nou (nu `1140886`/`1140888`/ +`1140885`). + +## 6. Fisiere temporare + +Nimic de sters in working copy. `test_writeback_buton1.FXP` si `test_writeback_buton1_log.txt` +din `COMUN\utile\Teste\editare_factura\` sunt artefacte normale, in acelasi tipar cu +`test_incarca_cursoare.FXP`/`test_page3_articole.FXP` deja existente in acel folder (folder de +teste, neurmarit ca binare in git). Fisierele de diagnostic SQL folosite pentru verificarea +independenta au fost in scratchpad-ul de sesiune (`C:\Users\...\Temp\claude\...\scratchpad\`), in +afara working copy - nu necesita curatare de catre urmatorul agent. + +Un fisier `test_baseline_isolation.prg`/`.FXP`/`_log.txt` exista in acelasi folder, creat inainte +de aceasta sesiune si NU de agentul curent - probabil al agentului paralel de pe alta sarcina; nu +l-am atins. diff --git a/docs/cercetare/handoff_watchdog_vfp.md b/docs/cercetare/handoff_watchdog_vfp.md new file mode 100644 index 0000000..7dc67f8 --- /dev/null +++ b/docs/cercetare/handoff_watchdog_vfp.md @@ -0,0 +1,127 @@ +# Handoff — watchdog VFP + bisectie blocaj "View Parameter" gnAn + +Predare la peste 310k context (Regula zero). **Doar stare, fara analize noi.** Raport analitic +complet (dovezi, log-uri, discutie): `docs\cercetare\rec_watchdog_vfp.md`. + +## 1. Inventar livrabile, cu fisier:linie + +**Utilitar**: `D:\ROA\ROAFACTURARE\COMUN\utile\Teste\watchdog_vfp.ps1`. Linia de comanda completa: + +``` +powershell -ExecutionPolicy Bypass -File "D:\ROA\ROAFACTURARE\COMUN\utile\Teste\watchdog_vfp.ps1" -Script "" -TimeoutSec 90 -AutoDismiss -MaxDialogs 10 +``` + +Lanseaza `vfp9.exe -A -T "