Price=0 is a valid state for kit components in crm_politici_pret_art, inserted automatically by the price sync system. Previously, the kit validation treated pret=0 the same as missing, blocking orders from importing even when all SKU mappings were correctly configured. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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
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/:
python -m uvicorn api.app.main:app --host 0.0.0.0 --port 5003
Sau folosind scriptul inclus:
./start.sh
Deschide http://localhost:5003 in browser.
Testare
Test A - Basic (fara Oracle):
python api/test_app_basic.py
Test C - Integrare Oracle:
python api/test_integration.py
Configurare (.env)
Copiaza .env.example si completeaza:
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, stergeINSTANTCLIENTPATH
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_vanzaresauid_pol_productiepe 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:
- Descarca comenzile din GoMag API (ultimele N zile, configurat in Setari)
- 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"
- Verifica daca comanda exista deja in Oracle → da: ALREADY_IMPORTED, nu: se importa
- Comenzi cu status ERROR din run-uri anterioare sunt reverificate in Oracle (crash recovery)
- Import in Oracle: cauta/creeaza partener → adrese → comanda
- 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
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
- Dashboard (
GET /api/dashboard/orders) — comenzile fara cache sunt verificate live si cached automat la fiecare request - Detaliu comanda (
GET /api/sync/order/{order_number}) — verifica Oracle live daca nu e cached - 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 (candperiod_days=0)status:all/IMPORTED/SKIPPED/ERROR/UNINVOICED/INVOICEDsearch,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
# 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)
# Ca Administrator
.\update.ps1
Sau manual:
cd C:\gomag-vending
git pull origin main
nssm restart GoMagVending
Configurare .env pe Windows
# 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 continetnsnames.ora(NU fisierul in sine)ORACLE_DSN= alias-ul exact dintnsnames.oraINSTANTCLIENTPATH= calea catre Oracle bin (thick mode, Oracle 10g/11g)FORCE_THIN_MODE=true= elimina necesitatea Instant Client (Oracle 12.1+)- Setarile din
.envpot fi suprascrise din UI →Setari→ salvate in SQLite
Serviciu Windows (NSSM)
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
# 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 --reloadnu functioneaza pe/mnt/e/(WSL2 limitation) — restarta manual- Serverul trebuie pornit din project root, nu din
api/ JSON_OUTPUT_DIRsiSQLITE_DB_PATHsunt relative la project root