Backend: - service_auto module complet: router, service, schemas, 5 teste suites (22/22 passed) - 5 endpoints: GET /ping, /firme, /tip-deviz, /masini, POST /comenzi - SP_CREEAZA_COMANDA_PROTOTIP creat în MARIUSM_AUTO (VALID, 5.9ms) - oracle_pool.py: session_callback backward-compat patch - ROA_WEB user: grants SP-only confirmate (H3), mariusm_test pool switchat - pyproject.toml: integration pytest marker înregistrat Frontend: - ComandaNoua.vue: date reale din Oracle (firme/tip-deviz/masini), nu hardcodate - src/modules/service-auto/services/api.js: axios service cu Bearer token - src/router/index.js: rută /service-auto/comanda-noua Docs: - decision-log.md: verdict MERGE, toate 6 ipoteze CONFIRMED - learnings.md: 7 patterns reutilizabile - grants-audit.md: arhitectura multi-tenant + proxy auth analysis + V_NOM_FIRME loop - template-modul-oracle.md: rețetă completă pentru module Oracle noi - TODO-phase2.md: 7 items concrete Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.9 KiB
Markdown
110 lines
3.9 KiB
Markdown
# Learnings — Service Auto Prototype
|
|
|
|
Consolidare din notele săptămânale. Fiecare pattern e aplicabil la orice modul Oracle nou.
|
|
|
|
---
|
|
|
|
## L1 — Sync-facade e suficient (nu trebuie true-async Oracle)
|
|
|
|
`oracledb.create_pool()` (sync) + `pool.acquire()` (sync) în `async def` FastAPI funcționează
|
|
perfect. True-async (`connect_async`) există și merge (22ms vs 33ms) dar nu aduce beneficii
|
|
măsurabile la latențele unui server local. Consistența cu `oracle_pool.py` existent > purism async.
|
|
|
|
**Pattern adoptat** (`shared/database/oracle_pool.py`):
|
|
```python
|
|
async with oracle_pool.get_connection('server_id') as conn:
|
|
with conn.cursor() as cur:
|
|
cur.execute(query, params)
|
|
```
|
|
|
|
---
|
|
|
|
## L2 — `cursor.var()` pentru OUT params, întotdeauna `int()` pe NUMBER
|
|
|
|
`cursor.var(oracledb.NUMBER).getvalue()` returnează `float`, nu `int`.
|
|
`cursor.var(oracledb.STRING).getvalue()` returnează `str | None`.
|
|
|
|
```python
|
|
out_id = cursor.var(oracledb.NUMBER)
|
|
out_nrord = cursor.var(oracledb.STRING)
|
|
cursor.callproc("SCHEMA.SP_NUMESC", [..., out_id, out_nrord])
|
|
id_result = int(out_id.getvalue()) # float → int obligatoriu
|
|
nr_result = out_nrord.getvalue() or ""
|
|
```
|
|
|
|
---
|
|
|
|
## L3 — RETURNING INTO evită coupling cu pack_sesiune
|
|
|
|
TRG_NOM_LUCRARI_BEFOINS și TRG_DEV_ORDL_BEFOINS populează `pack_sesiune` state (global
|
|
per-sesiune). Pentru un SP nou fără dependency pe `pack_sesiune`, folosește `RETURNING INTO`
|
|
local — trigger-ul rulează, dar citești ID-ul tău, nu din state-ul pachetului:
|
|
|
|
```sql
|
|
INSERT INTO NOM_LUCRARI (...) VALUES (...) RETURNING id_lucrare INTO v_id_lucrare;
|
|
INSERT INTO DEV_ORDL (..., id_lucrare) VALUES (..., v_id_lucrare) RETURNING id_ordl INTO p_id_ordl;
|
|
```
|
|
|
|
Zero dependențe, testabil cu ROLLBACK simplu.
|
|
|
|
---
|
|
|
|
## L4 — ORA-20xxx range pentru business errors, prefix stripped în handler
|
|
|
|
SP-urile aruncă business errors cu `-20001` … `-20999`.
|
|
Python handler strippuiește prefix-ul `ORA-2xxxx:` și returnează HTTP 422 cu mesajul curat:
|
|
|
|
```python
|
|
if 20001 <= code <= 20999:
|
|
clean = re.sub(r"^ORA-\d+:\s*", "", raw_message).strip()
|
|
raise HTTPException(status_code=422, detail=clean)
|
|
```
|
|
|
|
Mesajele cu diacritice (`ă î ș ț â`) trec corect prin NLS chain fără configurare explicită.
|
|
|
|
---
|
|
|
|
## L5 — ROA_WEB grants nu scalează per-obiect, dar se automatizează prin V_NOM_FIRME
|
|
|
|
Schema-level grants (Oracle 23ai+) nu există pe 21c. Proxy auth nu e opțiune (anulează
|
|
boundary SP-only). Soluția: grants per-obiect incluse **în deployment scripts existente**,
|
|
nu administrate separat.
|
|
|
|
**Firmă nouă** (după `impdp`): 1 script onboarding cu grant-urile curente pentru schema nouă.
|
|
|
|
**Obiect nou în toate schemele**: script companion la migrare care loopează `V_NOM_FIRME`:
|
|
```sql
|
|
BEGIN
|
|
FOR firm IN (SELECT DISTINCT schema FROM contafin_oracle.v_nom_firme WHERE schema IS NOT NULL)
|
|
LOOP
|
|
BEGIN EXECUTE IMMEDIATE 'GRANT EXECUTE ON ' || firm.schema || '.SP_NOUA TO ROA_WEB';
|
|
EXCEPTION WHEN OTHERS THEN NULL; END;
|
|
END LOOP;
|
|
END;
|
|
```
|
|
|
|
---
|
|
|
|
## L6 — Auth reuse zero shared code changes
|
|
|
|
Adăugarea unui server nou în `ORACLE_SERVERS` (`.env`) + `register_server()` în `main.py`
|
|
e suficientă. JWT conține automat `companies[]` din `V_NOM_FIRME` și `server_id`.
|
|
`AuthenticationMiddleware` injectează `request.state.user` fără modificări.
|
|
|
|
Testat: `server_id="mariusm_test"` → JWT cu `companies=["110","167","169"]` + `/ping` → `{server: "mariusm_test"}`.
|
|
|
|
---
|
|
|
|
## L7 — `Promise.allSettled` pentru lookup-uri paralele în Vue
|
|
|
|
Lookup-urile independente (firme, tip-deviz, masini) se încarcă în paralel.
|
|
`allSettled` (nu `all`) permite degradare gracefulă: dacă un endpoint pică, celelalte
|
|
se afișează totuși, iar utilizatorul vede un toast de avertizare specific.
|
|
|
|
```javascript
|
|
const [firmeRes, tipuriRes, masiniRes] = await Promise.allSettled([
|
|
api.getFirme(), api.getTipDeviz(), api.getMasini()
|
|
])
|
|
// Fiecare are .status === 'fulfilled' | 'rejected'
|
|
```
|