CLAUDE.md reduced from 214 to 60 lines — moved architecture, API endpoints, and detailed docs to README. Kept only AI-critical rules (TeamCreate, import flow gotchas, partner/pricing logic). README updated: added CANCELLED status, dual pricing policy, discount VAT splitting, stale error recovery, accurate partner/address logic, settings page references. Removed outdated Status Implementare section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
409 lines
16 KiB
Markdown
409 lines
16 KiB
Markdown
# 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
|
|
|
|
Facturile sunt verificate live din Oracle si cacate 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 cacate automat la fiecare request
|
|
2. **Detaliu comanda** (`GET /api/sync/order/{order_number}`) — verifica Oracle live daca nu e caat
|
|
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 necacturate → au primit factura? | Cacheaza datele facturii |
|
|
| Comenzi cacturate → 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
|