# GoMag Vending - Import Comenzi Web → ROA Oracle System automat de import comenzi din platforma GoMag in sistemul ERP ROA Oracle. ## Arhitectura ``` [GoMag API] → [Python Sync Service] → [Oracle PL/SQL] → [FastAPI Admin] ↓ ↓ ↑ ↑ JSON Orders Download/Parse/Import Store/Update Dashboard + Config ``` ### Stack Tehnologic - **API + Admin:** FastAPI + Jinja2 + Bootstrap 5.3 - **GoMag Integration:** Python (`gomag_client.py` — download comenzi cu paginare) - **Sync Orchestrator:** Python (`sync_service.py` — download → parse → validate → import) - **Database:** Oracle PL/SQL packages (IMPORT_PARTENERI, IMPORT_COMENZI) + SQLite (tracking) --- ## Quick Start ### Prerequisite - Python 3.10+ - Oracle Instant Client 21.x (optional — suporta si thin mode pentru Oracle 12.1+) ### Instalare ```bash pip install -r api/requirements.txt cp api/.env.example api/.env # Editeaza api/.env cu datele de conectare Oracle ``` ### Pornire server **Important:** serverul trebuie pornit **din project root**, nu din `api/`: ```bash python -m uvicorn api.app.main:app --host 0.0.0.0 --port 5003 ``` Sau folosind scriptul inclus: ```bash ./start.sh ``` Deschide `http://localhost:5003` in browser. ### Testare **Test A - Basic (fara Oracle):** ```bash python api/test_app_basic.py ``` **Test C - Integrare Oracle:** ```bash python api/test_integration.py ``` --- ## Configurare (.env) Copiaza `.env.example` si completeaza: ```bash cp api/.env.example api/.env ``` | Variabila | Descriere | Exemplu | |-----------|-----------|---------| | `ORACLE_USER` | User Oracle | `MARIUSM_AUTO` | | `ORACLE_PASSWORD` | Parola Oracle | `secret` | | `ORACLE_DSN` | TNS alias | `ROA_CENTRAL` | | `TNS_ADMIN` | Cale absoluta la tnsnames.ora | `/mnt/e/.../gomag/api` | | `INSTANTCLIENTPATH` | Cale Instant Client (thick mode) | `/opt/oracle/instantclient_21_15` | | `FORCE_THIN_MODE` | Thin mode fara Instant Client | `true` | | `SQLITE_DB_PATH` | Path SQLite (relativ la project root) | `api/data/import.db` | | `JSON_OUTPUT_DIR` | Folder JSON-uri descarcate | `api/data/orders` | | `APP_PORT` | Port HTTP | `5003` | | `ID_POL` | ID Politica ROA | `39` | | `ID_GESTIUNE` | ID Gestiune ROA | `0` | | `ID_SECTIE` | ID Sectie ROA | `6` | **Nota Oracle mode:** - **Thick mode** (Oracle 10g/11g): seteaza `INSTANTCLIENTPATH` - **Thin mode** (Oracle 12.1+): seteaza `FORCE_THIN_MODE=true`, sterge `INSTANTCLIENTPATH` --- ## Structura Proiect ``` gomag-vending/ ├── api/ # FastAPI Admin + Dashboard │ ├── app/ │ │ ├── main.py # Entry point, lifespan, logging │ │ ├── config.py # Settings (pydantic-settings + .env) │ │ ├── database.py # Oracle pool + SQLite schema + migrari │ │ ├── routers/ # Endpoint-uri HTTP │ │ │ ├── health.py # GET /health │ │ │ ├── dashboard.py # GET / (HTML) + /settings (HTML) │ │ │ ├── mappings.py # /mappings, /api/mappings │ │ │ ├── articles.py # /api/articles/search │ │ │ ├── validation.py # /api/validate/* │ │ │ └── sync.py # /api/sync/* + /api/dashboard/* + /api/settings │ │ ├── services/ │ │ │ ├── gomag_client.py # Download comenzi GoMag API │ │ │ ├── sync_service.py # Orchestrare: download→validate→import │ │ │ ├── import_service.py # Import comanda in Oracle ROA │ │ │ ├── mapping_service.py # CRUD ARTICOLE_TERTI + pct_total │ │ │ ├── sqlite_service.py # Tracking runs/orders/missing SKUs │ │ │ ├── order_reader.py # Citire gomag_orders_page*.json │ │ │ ├── validation_service.py │ │ │ ├── article_service.py │ │ │ ├── invoice_service.py # Verificare facturi ROA │ │ │ └── scheduler_service.py # APScheduler timer │ │ ├── templates/ # Jinja2 (dashboard, mappings, missing_skus, logs, settings) │ │ └── static/ # CSS (style.css) + JS (dashboard, logs, mappings, settings, shared) │ ├── database-scripts/ # Oracle SQL (ARTICOLE_TERTI, packages) │ ├── data/ # SQLite DB (import.db) + JSON orders │ ├── .env # Configurare locala (nu in git) │ ├── .env.example # Template configurare │ ├── test_app_basic.py # Test A - fara Oracle │ ├── test_integration.py # Test C - cu Oracle │ └── requirements.txt ├── logs/ # Log-uri aplicatie (sync_comenzi_*.log) ├── docs/ # Documentatie (PRD, stories) ├── screenshots/ # Before/preview/after pentru UI changes ├── start.sh # Script pornire (Linux/WSL) └── CLAUDE.md # Instructiuni pentru AI assistants ``` --- ## Dashboard Features ### Sync Panel - Start sync manual sau scheduler automat (5/10/30 min) - Progress live: `"Import 45/80: #CMD-1234 Ion Popescu"` - Smart polling: 30s idle → 3s cand ruleaza → auto-refresh tabela - Last sync clickabil → jurnal detaliat ### Comenzi - Filtru perioada: 3z / 7z / 30z / 3 luni / toate / custom - Status pills cu conturi totale pe perioada (nu per-pagina) - Cautare integrata in bara de filtre - Coloana Client cu tooltip `▲` cand persoana livrare ≠ facturare - Paginare sus + jos, selector rezultate per pagina (25/50/100/250) ### Mapari SKU - Badge `✓ 100%` / `⚠ 80%` per grup SKU - Filtru Complete / Incomplete - Verificare duplicat SKU-CODMAT (409 cu optiune de restaurare) ### SKU-uri Lipsa - Cautare dupa SKU sau nume produs - Filtru Nerezolvate / Rezolvate / Toate cu conturi - Re-scan cu progress inline si banner rezultat --- ## Fluxul de Import ``` 1. gomag_client.py descarca comenzi GoMag API → JSON files (paginat) 2. order_reader.py parseaza JSON-urile, sorteaza cronologic (cele mai vechi primele) 3. Comenzi anulate (GoMag statusId=7) → separate, sterse din Oracle daca nu au factura 4. validation_service.py valideaza SKU-uri: ARTICOLE_TERTI (mapped) → NOM_ARTICOLE (direct) → missing 5. Verificare existenta in Oracle (COMENZI by date range) → deja importate se sar 6. Stale error recovery: comenzi ERROR reverificate in Oracle (crash recovery) 7. Validare preturi + dual policy: articole rutate la id_pol_vanzare sau id_pol_productie 8. import_service.py: cauta/creeaza partener → adrese → importa comanda in Oracle 9. Invoice cache: verifica facturi + comenzi sterse din ROA 10. Rezultate salvate in SQLite (orders, sync_run_orders, order_items) ``` ### Statuses Comenzi | Status | Descriere | |--------|-----------| | `IMPORTED` | Importata nou in ROA in acest run | | `ALREADY_IMPORTED` | Existenta deja in Oracle, contorizata | | `SKIPPED` | SKU-uri lipsa → neimportata | | `ERROR` | Eroare la import (reverificate automat la urmatorul sync) | | `CANCELLED` | Comanda anulata in GoMag (statusId=7) | | `DELETED_IN_ROA` | A fost importata dar comanda a fost stearsa din ROA | **Regula upsert:** daca statusul existent este `IMPORTED`, nu se suprascrie cu `ALREADY_IMPORTED`. ### Reguli Business **Parteneri & Adrese:** - Prioritate partener: daca exista **companie** in GoMag (billing.company_name) → firma (PJ, cod_fiscal + registru). Altfel → persoana fizica, cu **shipping name** ca nume partener - Adresa livrare: intotdeauna din GoMag shipping - Adresa facturare: daca shipping name ≠ billing name → adresa shipping pt ambele; daca aceeasi persoana → adresa billing din GoMag - Cautare partener in Oracle: cod_fiscal → denumire → create new (ID_UTIL = -3) **Articole & Mapari:** - SKU lookup: ARTICOLE_TERTI (mapped, activ=1) are prioritate fata de NOM_ARTICOLE (direct) - SKU simplu: gasit direct in NOM_ARTICOLE → nu se stocheaza in ARTICOLE_TERTI - SKU cu repackaging: un SKU → CODMAT cu cantitate diferita (`cantitate_roa`) - SKU set complex: un SKU → multiple CODMAT-uri cu `procent_pret` (trebuie sum = 100%) **Preturi & Discounturi:** - Dual policy: articolele sunt rutate la `id_pol_vanzare` sau `id_pol_productie` pe baza contului contabil (341/345 = productie) - Daca pretul lipseste in politica, se insereaza automat pret=0 - Discount VAT splitting: daca `split_discount_vat=1`, discountul se repartizeaza proportional pe cotele TVA din comanda --- ## Facturi & Cache ### Sincronizari Sistemul are 3 procese de sincronizare si o setare de refresh UI: #### 1. Sync Comenzi (Dashboard → scheduler sau buton Sync) Procesul principal. Importa comenzi din GoMag in Oracle si verifica statusul celor existente. **Pasi:** 1. Descarca comenzile din GoMag API (ultimele N zile, configurat in Setari) 2. Valideaza SKU-urile fiecarei comenzi: - Cauta in ARTICOLE_TERTI (mapari manuale) → apoi in NOM_ARTICOLE (potrivire directa) - Daca un SKU nu e gasit nicaieri → comanda e marcata SKIPPED si SKU-ul apare in "SKU-uri lipsa" 3. Verifica daca comanda exista deja in Oracle → da: ALREADY_IMPORTED, nu: se importa 4. Comenzi cu status ERROR din run-uri anterioare sunt reverificate in Oracle (crash recovery) 5. Import in Oracle: cauta/creeaza partener → adrese → comanda 6. **Verificare facturi** (la fiecare sync): - Comenzi nefacturate → au primit factura in ROA? → salveaza serie/numar/total - Comenzi facturate → a fost stearsa factura? → sterge cache - Comenzi importate → au fost sterse din ROA? → marcheaza DELETED_IN_ROA **Cand ruleaza:** - **Automat:** scheduler configurat din Dashboard (interval: 5 / 10 / 30 min) - **Manual:** buton "Sync" din Dashboard sau `POST /api/sync/start` - **Doar facturi:** `POST /api/dashboard/refresh-invoices` (sare pasii 1-5) > Facturarea in ROA **nu** declanseaza sync — statusul se actualizeaza la urmatorul sync sau refresh manual. #### 2. Sync Preturi din Comenzi (Setari → on/off) La fiecare sync comenzi, daca este activat (`price_sync_enabled=1`), compara preturile din comanda GoMag cu cele din politica de pret Oracle si le actualizeaza daca difera. Configurat din: **Setari → Sincronizare preturi din comenzi** #### 3. Sync Catalog Preturi (Setari → manual sau zilnic) Sync independent de comenzi. Descarca **toate produsele** din catalogul GoMag, le potriveste cu articolele Oracle (prin CODMAT/SKU) si actualizeaza preturile in politica de pret. Configurat din: **Setari → Sincronizare Preturi** (activare + program) - **Doar manual:** buton "Sincronizeaza acum" din Setari sau `POST /api/price-sync/start` - **Zilnic la 03:00 / 06:00:** optiune in UI (**neimplementat** — setarea se salveaza dar scheduler-ul zilnic nu exista inca) #### Interval polling dashboard (Setari → Dashboard) Cat de des verifica **interfata web** (browser-ul) statusul sync-ului. Valoare in secunde (implicit 5s). **Nu afecteaza frecventa sync-ului** — e doar refresh-ul UI-ului. Facturile sunt verificate din Oracle si cached in SQLite (`factura_*` pe tabelul `orders`). ### Sursa Oracle ```sql SELECT id_comanda, numar_act, serie_act, total_fara_tva, total_tva, total_cu_tva, TO_CHAR(data_act, 'YYYY-MM-DD') FROM vanzari WHERE id_comanda IN (...) AND sters = 0 ``` ### Populare Cache 1. **Dashboard** (`GET /api/dashboard/orders`) — comenzile fara cache sunt verificate live si cached automat la fiecare request 2. **Detaliu comanda** (`GET /api/sync/order/{order_number}`) — verifica Oracle live daca nu e cached 3. **Refresh manual** (`POST /api/dashboard/refresh-invoices`) — refresh complet pentru toate comenzile ### Refresh Complet — `/api/dashboard/refresh-invoices` Face trei verificari in Oracle si actualizeaza SQLite: | Verificare | Actiune | |------------|---------| | Comenzi nefacturate → au primit factura? | Cached datele facturii | | Comenzi facturate → factura a fost stearsa? | Sterge cache factura | | Toate comenzile importate → comanda stearsa din ROA? | Seteaza status `DELETED_IN_ROA` | Returneaza: `{ checked, invoices_added, invoices_cleared, orders_deleted }` --- ## API Reference — Sync & Comenzi ### Sync | Method | Path | Descriere | |--------|------|-----------| | POST | `/api/sync/start` | Porneste sync in background | | POST | `/api/sync/stop` | Trimite semnal de stop | | GET | `/api/sync/status` | Status curent + progres + last_run | | GET | `/api/sync/history` | Istoric run-uri (paginat) | | GET | `/api/sync/run/{id}` | Detalii run specific | | GET | `/api/sync/run/{id}/log` | Log per comanda (JSON) | | GET | `/api/sync/run/{id}/text-log` | Log text (live din memorie sau reconstruit din SQLite) | | GET | `/api/sync/run/{id}/orders` | Comenzi run filtrate/paginate | | GET | `/api/sync/order/{number}` | Detaliu comanda + items + ARTICOLE_TERTI + factura | ### Dashboard Comenzi | Method | Path | Descriere | |--------|------|-----------| | GET | `/api/dashboard/orders` | Comenzi cu enrichment factura | | POST | `/api/dashboard/refresh-invoices` | Force-refresh stare facturi + deleted orders | **Parametri `/api/dashboard/orders`:** - `period_days`: 3/7/30/90 sau 0 (toate sau interval custom) - `period_start`, `period_end`: interval custom (cand `period_days=0`) - `status`: `all` / `IMPORTED` / `SKIPPED` / `ERROR` / `UNINVOICED` / `INVOICED` - `search`, `sort_by`, `sort_dir`, `page`, `per_page` Filtrele `UNINVOICED` si `INVOICED` fac fetch din toate comenzile IMPORTED si filtreaza server-side dupa prezenta/absenta cache-ului de factura. ### Scheduler | Method | Path | Descriere | |--------|------|-----------| | PUT | `/api/sync/schedule` | Configureaza (enabled, interval_minutes: 5/10/30) | | GET | `/api/sync/schedule` | Status curent | Configuratia este persistata in SQLite (`scheduler_config`). ### Settings | Method | Path | Descriere | |--------|------|-----------| | GET | `/api/settings` | Citeste setari aplicatie | | PUT | `/api/settings` | Salveaza setari | | GET | `/api/settings/sectii` | Lista sectii Oracle | | GET | `/api/settings/politici` | Lista politici preturi Oracle | **Setari disponibile:** `transport_codmat`, `transport_vat`, `discount_codmat`, `discount_vat`, `transport_id_pol`, `discount_id_pol`, `id_pol`, `id_pol_productie`, `id_sectie`, `split_discount_vat`, `gomag_api_key`, `gomag_api_shop`, `gomag_order_days_back`, `gomag_limit` --- ## Deploy Windows ### Instalare initiala ```powershell # Ruleaza ca Administrator .\deploy.ps1 ``` Scriptul `deploy.ps1` face automat: git clone, venv, dependinte, detectare Oracle, `start.bat`, serviciu NSSM, configurare IIS reverse proxy. ### Update cod (pull + restart) ```powershell # Ca Administrator .\update.ps1 ``` Sau manual: ```powershell cd C:\gomag-vending git pull origin main nssm restart GoMagVending ``` ### Configurare `.env` pe Windows ```ini # api/.env — exemplu Windows ORACLE_USER=VENDING ORACLE_PASSWORD=**** ORACLE_DSN=ROA TNS_ADMIN=C:\roa\instantclient_11_2_0_2 INSTANTCLIENTPATH=C:\app\Server\product\18.0.0\dbhomeXE\bin SQLITE_DB_PATH=api/data/import.db JSON_OUTPUT_DIR=api/data/orders APP_PORT=5003 ID_POL=39 ID_GESTIUNE=0 ID_SECTIE=6 GOMAG_API_KEY=... GOMAG_API_SHOP=... GOMAG_ORDER_DAYS_BACK=7 GOMAG_LIMIT=100 ``` **Important:** - `TNS_ADMIN` = folderul care contine `tnsnames.ora` (NU fisierul in sine) - `ORACLE_DSN` = alias-ul exact din `tnsnames.ora` - `INSTANTCLIENTPATH` = calea catre Oracle bin (thick mode, Oracle 10g/11g) - `FORCE_THIN_MODE=true` = elimina necesitatea Instant Client (Oracle 12.1+) - Setarile din `.env` pot fi suprascrise din UI → `Setari` → salvate in SQLite ### Serviciu Windows (NSSM) ```powershell nssm restart GoMagVending # restart serviciu nssm status GoMagVending # status serviciu nssm stop GoMagVending # stop serviciu nssm start GoMagVending # start serviciu ``` Loguri serviciu: `logs/service_stdout.log`, `logs/service_stderr.log` Loguri aplicatie: `logs/sync_comenzi_*.log` **Nota:** Userul `gomag` nu are drepturi de admin — `nssm restart` necesita PowerShell Administrator direct pe server. ### Depanare SSH ```bash # Conectare SSH (PowerShell remote, cheie publica) ssh -p 22122 gomag@79.119.86.134 # Verificare .env cmd /c type C:\gomag-vending\api\.env # Test conexiune Oracle C:\gomag-vending\venv\Scripts\python.exe -c "import oracledb, os; os.environ['TNS_ADMIN']='C:/roa/instantclient_11_2_0_2'; conn=oracledb.connect(user='VENDING', password='ROMFASTSOFT', dsn='ROA'); print('Connected!'); conn.close()" # Verificare tnsnames.ora cmd /c type C:\roa\instantclient_11_2_0_2\tnsnames.ora # Verificare procese Python Get-Process *python* | Select-Object Id,ProcessName,Path # Verificare loguri recente Get-ChildItem C:\gomag-vending\logs\*.log | Sort-Object LastWriteTime -Descending | Select-Object -First 3 # Test sync manual (verifica ca Oracle pool porneste) curl http://localhost:5003/health curl -X POST http://localhost:5003/api/sync/start # Refresh facturi manual curl -X POST http://localhost:5003/api/dashboard/refresh-invoices ``` ### Probleme frecvente | Eroare | Cauza | Solutie | |--------|-------|---------| | `ORA-12154: TNS:could not resolve` | `TNS_ADMIN` gresit sau `tnsnames.ora` nu contine alias-ul DSN | Verifica `TNS_ADMIN` in `.env` + alias in `tnsnames.ora` | | `ORA-04088: LOGON_AUDIT_TRIGGER` + `Nu aveti licenta pentru PYTHON` | Trigger ROA blocheaza executabile nelicențiate | Adauga `python.exe` (calea completa) in ROASUPORT | | `503 Service Unavailable` pe `/api/articles/search` | Oracle pool nu s-a initializat | Verifica logul `sync_comenzi_*.log` pentru eroarea exacta | | Facturile nu apar in dashboard | Cache SQLite gol — invoice_service nu a putut interoga Oracle | Apasa butonul Refresh Facturi din dashboard sau `POST /api/dashboard/refresh-invoices` | | Comanda apare ca `DELETED_IN_ROA` | Comanda a fost stearsa manual din ROA | Normal — marcat automat la refresh | | Scheduler nu porneste dupa restart | Config pierduta | Verifica SQLite `scheduler_config` sau reconfigureaza din UI | --- ## WSL2 Note - `uvicorn --reload` **nu functioneaza** pe `/mnt/e/` (WSL2 limitation) — restarta manual - Serverul trebuie pornit din **project root**, nu din `api/` - `JSON_OUTPUT_DIR` si `SQLITE_DB_PATH` sunt relative la project root