# Plan: Sincronizare Nomenclatoare Oracle + Auth SSO + OCR Furnizori ## Obiective 1. **Sincronizare nomenclatoare din Oracle în SQLite** (furnizori, casa/banca) 2. **Auth pentru data-entry-app** cu SSO (frontend-uri separate pe path) 3. **OCR: căutare furnizor după CUI** + creare locală dacă nu există 4. **Deploy Windows IIS** cu path routing --- ## Arhitectura Aleasă ``` roa2web.romfast.ro (IIS + ARR) │ ├── /reports/ → reports-app/frontend/ ├── /data/ → data-entry-app/frontend/ │ ├── /api/reports/* → reports-backend:8001 ├── /api/data/* → data-entry-backend:8003 └── /api/auth/* → reports-backend (auth provider) ``` **URL-uri compacte:** - `roa2web.romfast.ro/reports/` - Rapoarte - `roa2web.romfast.ro/data/` - Introducere date (bonuri fiscale) - `roa2web.romfast.ro/api/reports/` - API rapoarte - `roa2web.romfast.ro/api/data/` - API introducere date **SSO**: Același domeniu = localStorage partajat = token JWT valid pentru ambele --- ## Faza 1: Auth pentru Data-Entry-App ### 1.1 Backend - Integrare shared/auth/ **Fișiere de modificat:** - `data-entry-app/backend/app/main.py` - `data-entry-app/backend/app/routers/receipts.py` - `data-entry-app/backend/app/core/config.py` **Acțiuni:** ```python # main.py - Adăugare middleware import sys sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent.parent / "shared")) from auth.middleware import AuthenticationMiddleware from auth.dependencies import get_current_user app.add_middleware( AuthenticationMiddleware, excluded_paths=["/docs", "/redoc", "/openapi.json", "/health", "/"] ) ``` ```python # receipts.py - Înlocuire placeholder from auth.dependencies import get_current_user from auth.models import CurrentUser @router.get("/") async def list_receipts( current_user: CurrentUser = Depends(get_current_user) ): # folosește current_user.username ``` ### 1.2 Frontend - Auth Store + Login Page **Fișiere de creat/copiat din reports-app:** - `data-entry-app/frontend/src/stores/auth.js` (copiat) - `data-entry-app/frontend/src/views/LoginView.vue` (copiat) - `data-entry-app/frontend/src/router/index.js` (adăugat guard) - `data-entry-app/frontend/src/services/api.js` (axios interceptor) **Decizie SSO:** - Frontend data-entry folosește `/api/auth/login` de pe reports-backend - Sau: redirect la `/login` (reports-app) care setează token în localStorage - Token valid pentru ambele (același JWT_SECRET_KEY) --- ## Faza 2: Sincronizare Nomenclatoare Oracle → SQLite ### 2.1 Noi Modele SQLModel **Fișier:** `data-entry-app/backend/app/db/models/nomenclature.py` ```python class SyncedSupplier(SQLModel, table=True): """Furnizori sincronizați din Oracle""" __tablename__ = "synced_suppliers" id: int = Field(primary_key=True) # ID din Oracle (ID_PART) company_id: int = Field(index=True) name: str = Field(max_length=200) # DEN_PART fiscal_code: Optional[str] = Field(max_length=20, index=True) # COD_FISCAL address: Optional[str] = Field(max_length=500) synced_at: datetime = Field(default_factory=datetime.utcnow) class LocalSupplier(SQLModel, table=True): """Furnizori creați local din OCR (neexistenți în Oracle)""" __tablename__ = "local_suppliers" id: Optional[int] = Field(default=None, primary_key=True) company_id: int = Field(index=True) name: str = Field(max_length=200) fiscal_code: str = Field(max_length=20, unique=True, index=True) address: Optional[str] = Field(max_length=500) created_by: str = Field(max_length=100) created_at: datetime = Field(default_factory=datetime.utcnow) oracle_synced: bool = Field(default=False) # True când e creat în Oracle class SyncedCashRegister(SQLModel, table=True): """Case/Bănci sincronizate din Oracle""" __tablename__ = "synced_cash_registers" id: int = Field(primary_key=True) # ID din Oracle company_id: int = Field(index=True) name: str = Field(max_length=100) account_code: str = Field(max_length=20) # 5311, 5121 etc. register_type: str = Field(max_length=20) # CASA sau BANCA synced_at: datetime = Field(default_factory=datetime.utcnow) ``` ### 2.2 Alembic Migration **Fișier:** `data-entry-app/backend/migrations/versions/xxx_add_nomenclature_tables.py` ### 2.3 Sync Service **Fișier:** `data-entry-app/backend/app/services/sync_service.py` ```python class NomenclatureSyncService: """Sincronizare nomenclatoare din Oracle în SQLite""" @staticmethod async def sync_suppliers(company_id: int, schema: str) -> int: """Sincronizează furnizori pentru o companie""" async with oracle_pool.get_connection() as conn: cursor = conn.cursor() cursor.execute(f""" SELECT ID_PART, DEN_PART, COD_FISCAL, ADRESA FROM {schema}.NOM_PARTENERI WHERE TIP_PART IN ('F', 'A') -- Furnizori sau Ambele """) # Upsert în SQLite @staticmethod async def sync_cash_registers(company_id: int, schema: str) -> int: """Sincronizează case și bănci""" # Similar pentru NOM_CASE și NOM_BANCI @staticmethod async def get_schema_for_company(company_id: int) -> str: """Obține schema Oracle pentru o companie""" # Folosește cache din shared sau query V_NOM_FIRME ``` ### 2.4 Strategia de Sync Hibrid 1. **La startup app**: Sync automat (background task) 2. **Periodic**: Task programat la 4h 3. **On-demand**: Căutare live în Oracle când CUI nu există local **Fișier:** `data-entry-app/backend/app/main.py` ```python @app.on_event("startup") async def startup_sync(): # Background sync pentru company-urile active asyncio.create_task(sync_nomenclatures_background()) ``` --- ## Faza 3: OCR + Căutare Furnizor după CUI ### 3.1 Flow Căutare Furnizor ``` OCR extrage CUI ↓ Căutare în SyncedSupplier (SQLite) ↓ (nu găsit) Căutare în LocalSupplier (SQLite) ↓ (nu găsit) Căutare LIVE în Oracle (NOM_PARTENERI) ↓ (nu găsit) Creare LocalSupplier cu date OCR ↓ Utilizator poate edita înainte de submit ``` ### 3.2 Endpoint Căutare Furnizor **Fișier:** `data-entry-app/backend/app/routers/nomenclature.py` ```python @router.get("/suppliers/search") async def search_supplier( company_id: int, fiscal_code: Optional[str] = None, name: Optional[str] = None, current_user: CurrentUser = Depends(get_current_user) ) -> SupplierSearchResult: """ Caută furnizor: 1. În SQLite (synced + local) 2. Live în Oracle dacă nu găsit 3. Returnează sugestie creare dacă nu există """ @router.post("/suppliers/local") async def create_local_supplier( supplier: LocalSupplierCreate, current_user: CurrentUser = Depends(get_current_user) ) -> LocalSupplier: """Crează furnizor local din date OCR""" ``` ### 3.3 Modificare OCR Flow în Frontend **Fișier:** `data-entry-app/frontend/src/views/ReceiptCreateView.vue` ```javascript // După OCR, caută automat furnizor async function handleOCRResult(ocrData) { if (ocrData.cui) { const result = await api.get('/api/data-entry/suppliers/search', { params: { company_id: selectedCompany.id, fiscal_code: ocrData.cui } }); if (result.found) { form.partner_id = result.supplier.id; form.partner_name = result.supplier.name; } else { // Afișează opțiune creare locală showCreateSupplierDialog(ocrData); } } } ``` --- ## Faza 4: Deploy Windows IIS ### 4.1 Serviciu Windows pentru data-entry-backend **Fișier:** `deployment/windows/scripts/Install-DataEntry.ps1` Similar cu Install-ROA2WEB.ps1 dar: - ServiceName: `ROA2WEB-DataEntry` - Port: 8003 - BackendPath: `C:\inetpub\wwwroot\roa2web\data-entry-app\backend` - FrontendPath: `C:\inetpub\wwwroot\roa2web\data-entry-app\frontend` **Actualizare Install-ROA2WEB.ps1** pentru structura unitară: - BackendPath: `C:\inetpub\wwwroot\roa2web\reports-app\backend` - FrontendPath: `C:\inetpub\wwwroot\roa2web\reports-app\frontend` ### 4.2 Actualizare web.config **Fișier:** `deployment/windows/config/web.config` Reguli URL compacte (`/reports/`, `/data/`, `/api/reports/`, `/api/data/`): ```xml ``` **IIS Virtual Directories (pentru URL-uri compacte):** ```powershell # /reports/ → reports-app/frontend/ New-WebVirtualDirectory -Site "Default Web Site" -Name "reports" ` -PhysicalPath "C:\inetpub\wwwroot\roa2web\reports-app\frontend" # /data/ → data-entry-app/frontend/ New-WebVirtualDirectory -Site "Default Web Site" -Name "data" ` -PhysicalPath "C:\inetpub\wwwroot\roa2web\data-entry-app\frontend" ``` ### 4.3 Structura Foldere (UNITARĂ - identică dev/prod) **În development (git repo):** ``` roa2web/ ├── reports-app/ │ ├── backend/ # FastAPI port 8001 │ ├── frontend/ # Vue.js port 3000 │ └── telegram-bot/ # Bot Telegram ├── data-entry-app/ │ ├── backend/ # FastAPI port 8003 │ └── frontend/ # Vue.js port 3010 └── shared/ # Cod partajat (auth, database) ``` **În producție (Windows IIS) - IDENTIC:** ``` C:\inetpub\wwwroot\roa2web\ ├── reports-app/ │ ├── backend/ # Serviciu Windows port 8001 │ └── frontend/ # Servit de IIS pe / ├── data-entry-app/ │ ├── backend/ # Serviciu Windows port 8003 │ └── frontend/ # Servit de IIS pe /data-entry/ ├── telegram-bot/ # Serviciu Windows port 8002 └── shared/ # Cod partajat ``` **Avantaje structură unitară:** - Deploy simplu: `xcopy /E /Y source\reports-app dest\reports-app` - Path-uri identice în cod (no surprises) - Un singur script de deploy pentru ambele medii --- ## Faza 5: Configurare Dev (identic cu prod) ### 5.1 Vite Config pentru URL-uri Compacte **Fișier:** `data-entry-app/frontend/vite.config.js` ```javascript export default defineConfig({ base: '/data/', // URL compact în producție server: { proxy: { '/api/auth': 'http://localhost:8001', '/api/data': 'http://localhost:8003' } } }) ``` **Fișier:** `reports-app/frontend/vite.config.js` (ACTUALIZAT) ```javascript export default defineConfig({ base: '/reports/', // URL compact în producție (era '/') server: { proxy: { '/api/auth': 'http://localhost:8001', '/api/reports': 'http://localhost:8001' } } }) ``` **IMPORTANT:** Actualizare API calls în frontend: - Reports: `/api/reports/companies`, `/api/reports/invoices`, etc. - Data Entry: `/api/data/receipts`, `/api/data/suppliers`, etc. - Auth (comun): `/api/auth/login`, `/api/auth/refresh` ### 5.2 Script Start Unificat **Fișier:** `start-all.sh` (nou) ```bash #!/bin/bash # Pornește toate serviciile pentru dev # SSH tunnel ./ssh_tunnel.sh start # Reports backend cd reports-app/backend && uvicorn app.main:app --port 8001 & # Data entry backend cd data-entry-app/backend && uvicorn app.main:app --port 8003 & # Reports frontend cd reports-app/frontend && npm run dev -- --port 3000 & # Data entry frontend cd data-entry-app/frontend && npm run dev -- --port 3010 & wait ``` --- ## Ordine Implementare | # | Task | Efort | Dependențe | |---|------|-------|------------| | 1 | Modele SQLModel nomenclatoare | 30 min | - | | 2 | Alembic migration | 15 min | #1 | | 3 | Sync service (Oracle → SQLite) | 2h | #2 | | 4 | Auth middleware în data-entry-backend | 1h | - | | 5 | Auth store + login în data-entry-frontend | 1h | #4 | | 6 | Endpoint căutare furnizor | 1h | #3 | | 7 | Frontend OCR + furnizor flow | 1.5h | #6 | | 8 | web.config IIS actualizat | 30 min | - | | 9 | Script deploy data-entry Windows | 1h | #8 | | 10 | Testare end-to-end | 1h | all | **Total estimat: ~10h** --- ## Fișiere Critice de Modificat/Creat ### Backend data-entry-app: - `app/main.py` - middleware auth + startup sync - `app/db/models/nomenclature.py` - noi modele (CREARE) - `app/services/sync_service.py` - sync Oracle (CREARE) - `app/services/nomenclature_service.py` - refactorizare - `app/routers/nomenclature.py` - endpoint-uri noi (CREARE) - `app/routers/receipts.py` - auth dependencies - `migrations/versions/xxx_nomenclature.py` - migrare (CREARE) ### Frontend data-entry-app: - `src/stores/auth.js` - copiat din reports-app - `src/views/LoginView.vue` - copiat + adaptat - `src/router/index.js` - auth guard - `src/services/api.js` - axios config - `src/views/ReceiptCreateView.vue` - OCR + supplier flow ### Deploy (structură unitară): - `deployment/windows/config/web.config` - reguli noi + actualizate - `deployment/windows/scripts/Install-ROA2WEB.ps1` - ACTUALIZAT pentru structura unitară - `deployment/windows/scripts/Install-DataEntry.ps1` - NOU - `deployment/windows/scripts/Build-ROA2WEB.ps1` - ACTUALIZAT pentru ambele apps - `deployment/windows/docs/WINDOWS_DEPLOYMENT.md` - ACTUALIZAT cu noua structură ### Shared: - Nu necesită modificări (refolosim exact ce există) --- ## Întrebări Rezolvate | Întrebare | Răspuns | |-----------|---------| | Furnizor nou din OCR? | Creare automată în SQLite (LocalSupplier) | | Sync strategy? | Hibrid: startup + periodic 4h + on-demand | | Auth sharing? | Frontend-uri separate pe path, același token JWT (SSO via localStorage) | | Deployment? | IIS path routing, servicii Windows separate | | Structura directoare? | **UNITARĂ** - grupat pe app (`{app}/backend`, `{app}/frontend`) identic dev/prod | | SSO cum funcționează? | Același domeniu IIS → localStorage partajat → token valid pentru ambele API-uri | | URL-uri? | **COMPACTE**: `/reports/`, `/data/`, `/api/reports/`, `/api/data/` | | Root (/)? | Redirect automat la `/reports/` |