# Harta cod: adaugare/editare FACTURA si AVIZ (read-only) Sursa: text FoxBin2Prg deja in-tree (`.vc2`/`.prg`), citit cu `Grep`/`Read` si indexat cu `vfp_symbols.ps1` (`-CacheRoot/-ProjectRoot D:\ROA\ROAFACTURARE`, `-IndexFile D:\ROA\_vfp_textcache\roafacturare\_symbols.tsv`). Nicio modificare de fisier. ## 1. Formulare si clase implicate Nu exista formular `.scx` pentru factura/aviz in acest proiect (`.scx`-urile din arbore sunt doar utilitare: `frm_borderou_facturi.scx`, `frm_import_efactura.scx` etc.). Ecranul de adaugare/editare e o **clasa `.vcx` instantiata prin `Createobject()`**, nu un `DO FORM`: - **`frm_facturare_articole2`** — `COMUN\clase\ofacturare.vcx` (text `COMUN\clase\ofacturare.vc2:15936-22614`). Formularul unificat, curent, folosit in productie pentru factura SI aviz. Instantiat in `COMUN\programe\ofacturare.prg:248` (`ofrmdetaliifactura = Createobject('frm_facturare_articole2')`) si in `COMUN\clase\ofacturare_comun.vc2:4028`, doar cand `llFacturareNoua` e adevarat (`COMUN\programe\ofacturare.prg:246-248`). - **`frm_facturare_articole`** — acelasi fisier, `ofacturare.vc2:11123-15936`. Varianta mai veche; in cod de productie nu mai e instantiata (doar in probele din `COMUN\utile\Teste\facturare_unificat\*.prg` si `COMUN\utile\Teste\editare_factura\creeaza_documente_s8.prg:571`, `emite_document_stoc_s93.prg:264`) — ramane in clasa dar calea curenta e `frm_facturare_articole2`. - **Flux vechi, ne-unificat** (`llFacturareNoua = .F.`, `ofacturare.prg:255-266`) — alege un formular de antet separat pe tip de document, fara grila unificata de articole: - `frm_date_aviz_lucrare` — `ofacturare.vc2:7775` — pentru `tnTip` 27 sau 30. - `frm_date_factura` — `ofacturare.vc2:8637` — pentru `tnTip < 21` sau in `(45,48,49,51,52)`. - `frm_date_aviz` — `ofacturare.vc2:6721` — altfel (aviz). - **`oAntetFacturare`** (`Define Class ... As Custom`) — `COMUN\programe\ofacturare_antet.prg:14`. Logica de antet a lui `frm_facturare_articole2`: cautari (`do_cauta_*`), validare (`valideaza_antet`), schimbarea tipului de document (`alege_tipdoc`, `schimba_tipdoc`). Header-ul fisierului (`ofacturare_antet.prg:1-7`) spune explicit ca a fost **portata din `frm_date_factura`/`frm_date_aviz`** cand s-a facut unificarea. Instantiata in `frm_facturare_articole2.Init`: `This.oAntet = Createobject('oAntetFacturare')` (`ofacturare.vc2:21365`). - **`oDateFactura`** (`poDate`) — `COMUN\programe\ofacturare_comun.prg:131`. Obiectul de stare al documentului curent (creat in `ofacturare.prg:196`); tine `nIdTipDoc`, `nIdTipDocFactura`(=5), `nIdTipDocAvizExpeditie`(=6), `id_gestiune_init`, `listaid`, `id_pol`, `zi_curs` etc. - **`ofacturare_editare.prg`** (`COMUN\programe\ofacturare_editare.prg`, 1444 linii) — flux **separat**, pentru editarea liniilor unei facturi/aviz deja emise (nu formularul de creare): `IncarcaAntetFacturaEditare` (63), `IncarcaLiniiFacturaEditare` (209), `PregatesteArticoleFacturaEditare` (605), `ScrieArticoleFacturaEditate` (741), clasa `ArticoleNotaEditor As Custom` (1226). Inregistrat via `Set Procedure To ofacturare_editare.prg Additive` (`Programe\roafacturare.prg:219`). - **`combosql_cautare`** — clasa de baza pentru comboboxurile de cautare din grid (vezi pct. 2), `ofacturare.vc2:407-531`, mostenind `combosql As combobox` din `COMUN\clase\_cb_base.vc2:519`. ## 2. Zona de introducere articole (cautare cod material / denumire) In grila `grd_factura` a lui `frm_facturare_articole2`, coloanele au cate un combobox de tip `combosql_cautare`: - `grd_factura.cCodMat.cboCodmat` — `ADD OBJECT` la `ofacturare.vc2:17524-17537`, `ncharcountbegin = 2`, `pcursorname = crsCodmat`, `pfieldactiv = codmat`. - `grd_factura.cDenumire.cCboDenumire` — `ADD OBJECT` la `ofacturare.vc2:17560-17574`, `ncharcountbegin = 2`, `pcursorname = crsDenumire`, `pfieldactiv = denumire`. **Pragul de caractere**: logica e in `combosql_cautare.refreshdata` (`ofacturare.vc2:463-496`): ``` lnTip = Iif(Type('tnTip') = 'N', m.tnTip, 0) If m.lnTip <> 0 And Len(Alltrim(This.cSearchString)) <= This.nCharCountBegin Return .T. && nu cauta, iese Endif ``` Cu `ncharcountbegin = 2` pe ambele combo-uri, filtrarea porneste abia cand `Len(cSearchString) > 2`, adica de la **al treilea caracter tastat**. **Interactivechange/Keypress**: nu sunt suprascrise in `combosql_cautare` — vin din parintele `combosql` (`COMUN\clase\_cb_base.vc2:519`): - `InteractiveChange` (linia 606-608): doar reseteaza `cSearchString`. - `KeyPress` (linia 610-776): construieste `csearchstring` caracter cu caracter (sageti, Del/Backspace tratate separat), apoi cheama `This.RefreshData(1)` = cautare "incepe cu" (linia 720) sau, la Ctrl+Enter, `RefreshData(2)` = cautare "contine" (liniile 636-649). **Filtrul/SQL aplicat**: `refreshdata` seteaza `cfiltrucod`/`cfiltruden` in functie de tip (`ofacturare.vc2:476-489`): ``` Case lnTip = 1 (incepe cu): cfiltrucod/cfiltruden = cSearchString + '%' Case lnTip = 2 (contine): cfiltrucod/cfiltruden = '%' + cSearchString + '%' ``` apoi `cursor_preturi_call()` (`ofacturare.vc2:446-461`) construieste apelul catre pachetul Oracle `pack_facturare.cursor_preturi(?poDate.zi_curs,?poDate.tip,?poDate.id_valuta, ?poDate.id_gestiune_init sau ?poDate.listaid,?gnLuna,?gnAn,?gnIdUtil,?gnIdSucursala, ?pcFiltruCod,?pcFiltruDen)` (sau `pack_facturare.cursor_gestiune(...)` cand `poDate.tip = 41`), executat prin `goExecutor.oExecuta` in `selectdata` (linia 514-529). Corpul PL/SQL al pachetului nu e in acest repo VFP (schema Oracle) — nu l-am putut citi read-only de aici. Proprietatile `csourcesql`/`csourcewhere` de pe `ADD OBJECT` (ex. `select codmat, denumire, ... from vnom_articole ... where inactiv = 0`) sunt doar sablonul design-time al cursorului gol (`creeaza_cursor_gol`, linia 426), nu interogarea reala rulata la tastare. **Sincronizare cod<->denumire** dupa alegere: `grd_factura.cCodMat.cboCodmat.LostFocus` (`ofacturare.vc2:22088-22109`) si perechea ei `cDenumire.cCboDenumire.LostFocus` (22115-22136) cheama `thisform.do_adauga_articol_cautat(loArticol, loArticol.cantitate)` si fortez re-creerea cursorului celeilalte combo (seteaza `cSearchString` cu valoarea gasita si reseteaza `RowSource`), ca sa afiseze articolul ales in ambele coloane. ## 3. Lista de preturi / politici de pret - `poDate.id_gestiune_init` e parametrul implicit trimis la `pack_facturare.cursor_preturi`; cand `poDate.tip = 45` se trimite `poDate.listaid` in loc (`ofacturare.vc2:453-457`). - De ce poate aparea un articol de mai multe ori: interogarea serverului (Oracle, `pack_facturare.cursor_preturi`) uneste nomenclatorul de articole cu politicile de pret — confirmarea client-side ca politicile sunt cheia vine din `modifica_lista_preturi` (`ofacturare.vc2:21735-21837`), care interogheaza direct tabelele de politici: `Select id_pol_art, id_venchelt, proc_tvav from crm_politici_pret_art where id_pol = ... and id_articol = ...` (linia 21755) si scrie prin `pack_preturi.adauga_politica_pret_art`/ `pack_preturi.modifica_pret_pol_pret_art` (21779, 21792). Corpul exact al join-ului din `cursor_preturi` e in PL/SQL, nevizibil din acest working copy VFP — afirmatia despre "un rand per politica aplicabila" e o inferenta din tabelele folosite, nu o citire directa a interogarii. ## 4. Zona de jos: controale de discount/totaluri Toate sunt copii directe ale formularului `frm_facturare_articole2` (fara container intermediar), clasa `_textbox` din `_baza.vcx`, asezate sub grila (Top ~742-792): | Obiect | Top | Left | Width | Anchor | Note | |---|---|---|---|---|---| | `tx_total_baza_nat` (18123) | 745 | 17 | (implicit) | 4 | ReadOnly, `ControlSource=thisform.nbazaron` | | `tx_total_baza_val` (18137) | 789 | 17 | (implicit) | 4 | ReadOnly, `thisform.nbazaval` | | `tx_disc_factura_nat` (18082) | 745 | 351 | (implicit) | 4 | ReadOnly, `thisform.ndiscfactron` | | `tx_disc_factura_procent` (18096) | 767 | 542 | 54 | 4 | editabil, `Value=0` | | `tx_disc_factura_val` (18109) | 789 | 351 | (implicit) | 4 | ReadOnly, `thisform.ndiscfactval` | | `tx_total_tva_nat` (18181) | 742 | 689 | (implicit) | 12 | ReadOnly, `thisform.ntvaron` | | `tx_total_tva_val` (18195) | 792 | 689 | (implicit) | 12 | ReadOnly, `thisform.ntvaval` | | `tx_total_factura_nat` (18151) | 742 | 833 | (implicit) | 12 | ReadOnly, FontBold, `thisform.ntotalron` | | `tx_total_factura_val` (18166) | 792 | 835 | (implicit) | 12 | ReadOnly, FontBold, `thisform.ntotalval` | (linii = `COMUN\clase\ofacturare.vc2`). `Anchor=4` = ancorat de Bottom (coboara odata cu formularul); `Anchor=12` (4+8) = Bottom+Right, pe coloana totalurilor din dreapta. Niciun `Height`/`Width` explicit pe majoritatea — mostenesc default-ul clasei `_textbox`; doar `tx_disc_factura_procent` are `Width=54` explicit. Nu am gasit cod care repozitioneaza aceste controale la runtime in `frm_facturare_articole2` — pozitionarea e strict design-time + `Anchor` (fara logica de `Resize`/`Move` pe ele; `Resize` al formularului, linia 21847-21854, nu le atinge explicit). ## 5. Diferente factura vs aviz pe `frm_facturare_articole2` - **Discriminator unic**: `poDate.nIdTipDoc`, comparat cu `poDate.nIdTipDocFactura` (=5) / `poDate.nIdTipDocAvizExpeditie` (=6). Helper: `oAntetFacturare.EsteAviz()` (`ofacturare_antet.prg:16-18`): `Return poDate.nIdTipDoc = poDate.nIdTipDocAvizExpeditie`. - **Setare initiala**, dupa `tnTip` primit la deschidere (`COMUN\programe\ofacturare.prg:196-206`): ``` Case tnTip = 27 or 30 -> nIdTipDoc = 6 (AVIZ) Case tnTip < 21 sau tnTip in (45,48,49,51,52) -> nIdTipDoc = 5 (FACTURA) Otherwise -> nIdTipDoc = 6 (AVIZ) ``` - **Schimbare interactiva**: combo `clb_fdoc.cboFdoc` -> `do_schimba_tipdoc` (`ofacturare.vc2:20046-20071`) -> `oAntet.alege_tipdoc(valoare_combo)` mapeaza textul ("FACTURA"/"AVIZ"/"PROFORMA"/"BON FISCAL") pe id-ul de tip, apoi `oAntet.schimba_tipdoc(id)` (`ofacturare_antet.prg:889-923`) actualizeaza `poDate.nIdTipDoc` si seriile. Exista o garda: daca documentul e proforma si au fost deja adaugate articole, schimbarea tipului e blocata cu `amessagebox` (`ofacturare.vc2:20050-20055`). - **Ramuri `EsteAviz()` in `oAntetFacturare`** (aceleasi metode de cautare/validare servesc ambele tipuri, dar cu cai diferite): `do_cauta_altele` (20-45), `do_cauta_client` (97-100), `do_cauta_comanda` (240-243), `do_cauta_contract` (296-299), `do_cauta_gestiune_init` (518-521), `do_cauta_lucrare` (553-556), `do_cauta_sectie` (633-636), `do_cauta_venchelt` (708-711), `valideaza_antet` (762-766) — toate in `COMUN\programe\ofacturare_antet.prg`. - **Fluxul vechi (ne-unificat)** folosea deja formulare separate per tip pentru antet (`frm_date_factura` / `frm_date_aviz` / `frm_date_aviz_lucrare`, vezi pct. 1) — unificarea descrisa in header-ul `ofacturare_antet.prg` a mutat logica lor comuna intr-o singura clasa, ramura aleasa la runtime pe `poDate.nIdTipDoc`.