Refactor izolare multi-tenant:
- Schema Oracle rezolvată din id_firma via CONTAFIN_ORACLE.V_NOM_FIRME (cached 24h)
- server_id propagat din JWT (request.state.server_id) la oracle_pool.get_connection
- Elimină _SCHEMA='MARIUSM_AUTO' și literal 'mariusm_test' din toate query-urile
- Autorizare firmă la router (_company_id): 403 dacă id_firma nu e în JWT companies[]
Tier 3 — lookup endpoints cached 24h:
- GET /asiguratori (DEV_NOM_ASIGURATORI ← NOM_PARTENERI)
- GET /inspectori?id_asigurator=N (DEV_NOM_INSPECTORI per asig)
- GET /operatii (DEV_NOM_NORME)
- GET /parteneri?q=... (typeahead LIKE escape)
- GET /masini/{id}/detalii (VIN, cilindree, putere)
- POST /comenzi: PACK_SERII_NUMERE.aloca_numar + compensating dezaloca;
pc_nr VFP-format prefix+seq/nrinmat; ORA-06512 stripped din detail
D1 PartnerCreateDialog (nou):
- POST /api/service-auto/parteneri → PartnerCreateRequest; 409 pe CUI
duplicat (NOM_PARTENERI fără UNIQUE constraint — check manual);
id_part = MAX+1 cu retry pe ORA-00001 (fără sequence în schema VFP legacy)
- Frontend PartnerCreateDialog.vue — PrimeVue, design tokens, dark-mode safe
- Integrat în ComandaNoua.vue via AutoComplete empty-action hook
Shared AsyncAutoComplete (nou):
- src/shared/components/AsyncAutoComplete.vue — typeahead async debounced
cu emptyAction slot, force-selection, keyboard (Enter/Esc), design tokens
- ComandaNoua.vue refactorizat să folosească shared component
- SupplierDualField (data-entry) skipped — documentat în
docs/service-auto/autocomplete-dual-decision.md (pattern diferit)
Mobile chrome (CLAUDE.md):
- ComandaNoua.vue + ComenziBrowseView.vue: MobileTopBar, BottomSheet
filtre, MobileBottomNav, card list, isMobile resize listener
Migrații grant-uri idempotente:
- ff_2026_04_13_01_AUTO.sql — SELECT/EXECUTE pe tabele Tier 3 + index
IX_NOM_PARTENERI_DEN_UPPER
- ff_2026_04_13_02_AUTO.sql — INSERT pe NOM_PARTENERI pentru D1
Live smoke pe MARIUSM_AUTO: /ping 1ms, /tip-deviz 7, /masini 261,
POST /parteneri id_part=70241, firma neautorizată → 403.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
66 lines
2.3 KiB
Python
66 lines
2.3 KiB
Python
"""
|
|
Multi-tenant context resolver for service_auto.
|
|
|
|
Pattern-ul urmează `modules/reports`:
|
|
- `server_id` vine din JWT (`request.state.server_id`), propagat la `oracle_pool.get_connection(server_id)`.
|
|
- `schema` se rezolvă din `CONTAFIN_ORACLE.V_NOM_FIRME` bazat pe `id_firma`, pe serverul utilizatorului.
|
|
- Rezultatul e cached in-process 24h per (company_id, server_id).
|
|
|
|
NU introduce hardcodări de schemă sau server_id în service_auto. Toate query-urile SQL trebuie să
|
|
folosească `f"{schema}.{TABLE}"`, iar toate `get_connection()` trebuie să primească `server_id`.
|
|
"""
|
|
import time
|
|
from typing import Optional, Tuple
|
|
|
|
import oracledb
|
|
from fastapi import HTTPException
|
|
|
|
from shared.database.oracle_pool import oracle_pool
|
|
|
|
from .. import logger
|
|
|
|
_SCHEMA_TTL = 86400 # 24h — schema / firma binding changes via DB migration, not runtime
|
|
_schema_cache: dict = {}
|
|
|
|
|
|
async def get_schema(company_id: int, server_id: Optional[str]) -> str:
|
|
"""
|
|
Rezolvă schema Oracle pentru o firmă pe serverul curent al utilizatorului.
|
|
|
|
Query pe `CONTAFIN_ORACLE.V_NOM_FIRME` (prezent pe fiecare server în arhitectura ROA2WEB).
|
|
Cached per (company_id, server_id) 24h.
|
|
|
|
Raises 422 dacă firma nu există pe serverul respectiv (misconfiguration).
|
|
"""
|
|
key = (company_id, server_id or "")
|
|
entry: Optional[Tuple[float, str]] = _schema_cache.get(key)
|
|
if entry and (time.monotonic() - entry[0]) < _SCHEMA_TTL:
|
|
return entry[1]
|
|
|
|
try:
|
|
async with oracle_pool.get_connection(server_id) as conn:
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"SELECT schema FROM CONTAFIN_ORACLE.V_NOM_FIRME WHERE id_firma = :id",
|
|
{"id": company_id},
|
|
)
|
|
row = cur.fetchone()
|
|
except oracledb.DatabaseError:
|
|
logger.error("service_auto._context.get_schema Oracle error", exc_info=True)
|
|
raise HTTPException(status_code=503, detail="Eroare la rezolvarea schemei firmei")
|
|
|
|
if not row or not row[0]:
|
|
raise HTTPException(
|
|
status_code=422,
|
|
detail=f"Firma {company_id} nu are schemă configurată pe serverul curent.",
|
|
)
|
|
|
|
schema = row[0]
|
|
_schema_cache[key] = (time.monotonic(), schema)
|
|
return schema
|
|
|
|
|
|
def reset_schema_cache() -> None:
|
|
"""Test helper — clear the schema cache."""
|
|
_schema_cache.clear()
|