Files
roa2web-service-auto/docs/service-auto/learnings.md
Claude Agent 32aca55c78 feat(service-auto): săpt 3-phase2 — toate ipotezele confirmate + modul funcțional
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>
2026-06-05 09:37:09 +00:00

3.9 KiB

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):

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.

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:

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:

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:

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.

const [firmeRes, tipuriRes, masiniRes] = await Promise.allSettled([
  api.getFirme(), api.getTipDeviz(), api.getMasini()
])
// Fiecare are .status === 'fulfilled' | 'rejected'