Files
mini-erp/mini-backend/docs/sap-proxy.md
T
2026-08-17 11:58:38 -05:00

5.9 KiB

Flujo de Proxy SAP

Función Central: forward_to_sapb1

Todas las operaciones CRUD pasan por esta función en core/sap.py, que actúa como proxy hacia SAP Service Layer. La mayoría de los routers (business_partners, items, sales, purchases, batch, parte de catalogs) no la llaman directamente: pasan por core/crud_router.py, una factory que arma el list/get/create/update de un recurso OData y delega en forward_to_sapb1 (ver Arquitectura). El proxy hacia el API Gateway de Crystal Reports (core/api_gateway.py, usado por routers/apigateway.py) sigue el mismo patrón de sesión-global + reintento en 401, mostrado más abajo bajo "Otros proxies".

flowchart TD
    START([Inicio]) --> INPUT["method, endpoint_path,\njson_data, params, headers"]
    INPUT --> BUILD["Construye URL:\nSAPB1_SL_URL / endpoint_path"]
    BUILD --> HEADERS["Adjunta headers:\nCookie: B1SESSION=...\nContent-Type: application/json"]
    HEADERS --> SEND[Envía request a SAP]

    SEND --> CHECK{Respuesta}
    CHECK -->|2xx| SUCCESS[Retorna Response al router]
    CHECK -->|401| REFRESH["Force refresh:\nensure_sap_session()"]
    CHECK -->|Timeout / Error| FAIL[ConnectionError]

    REFRESH --> RETRY{Es reintento?}
    RETRY -->|No| SEND2["Reintenta request\ncon nueva sesión"]
    RETRY -->|Sí| ERROR["Extrae error.message.value\nde SAP y lanza HTTPException"]

    SEND2 --> CHECK2{Respuesta}
    CHECK2 -->|2xx| SUCCESS
    CHECK2 -->|4xx/5xx| ERROR

    SUCCESS --> END([Fin])
    FAIL --> END
    ERROR --> END

    style ERROR fill:#f96,stroke:#a33

Diagrama de Secuencia — Operación CRUD Típica

Ejemplo con GET /api/Items:

sequenceDiagram
    participant F as Frontend
    participant MW as Middleware
    participant R as router/items.py
    participant C as core/sap.py
    participant SAP as SAP Service Layer

    F->>MW: GET /api/Items?$skip=0&$top=20\nX-Session-Token: [token]
    MW->>MW: Valida sesión
    MW->>R: request.state.emp_id = "123"

    R->>C: forward_to_sapb1(\n  GET, "Items?$skip=0&$top=20&$inlinecount=allpages")

    C->>SAP: GET /b1s/v1/Items?$skip=0&$top=20\nCookie: B1SESSION=abc123
    SAP-->>C: 200 - value: [...], odata.count: 150

    C-->>R: Response
    R-->>F: 200 - data: [...], total: 150

Re-autenticación Automática ante 401

Cuando SAP rechaza la sesión actual (401), el sistema renueva automáticamente la sesión de SAP y reintenta:

sequenceDiagram
    participant R as Router
    participant C as core/sap.py
    participant SAP as SAP Service Layer

    R->>C: forward_to_sapb1(GET, "Items?...")
    C->>SAP: GET /b1s/v1/Items?...\nCookie: B1SESSION=abc123
    SAP-->>C: 401 Unauthorized

    Note over C: Sesión SAP expirada\n→ Re-autentica y reintenta

    C->>SAP: POST /Login\nUserName: manager, Password: ..., CompanyDB: ...
    SAP-->>C: 200 SessionId: xyz789
    C->>C: Actualiza sap_session_store

    C->>SAP: GET /b1s/v1/Items?...\nCookie: B1SESSION=xyz789
    SAP-->>C: 200 - value: [...]

    C-->>R: Response 200

    Note over R,C: Si el reintento también falla (4xx/5xx)\n→ se extrae error.message.value\n→ se lanza HTTPException

Parámetros OData y Headers SAP Soportados

Query Parameters (OData)

Parámetro Uso Ejemplo
$skip Paginación — desplazamiento $skip=20
$top Paginación — límite por página $top=20
$select Selección de campos $select=ItemCode,ItemName
$filter Filtrado $filter=ItemCode eq 'A001'
$orderby Ordenamiento $orderby=ItemCode asc
$inlinecount Contar registros totales $inlinecount=allpages

Headers SAP

Header Función
B1S-PageSize Tamaño de página personalizado
B1S-CaseInsensitive Búsqueda sin distinción mayúsc/minúscula
B1S-ReplaceCollectionsOnPatch Reemplazar colecciones completas en PATCH

Manejo de Errores

Situación Comportamiento
SAP devuelve 2xx Retorna Response al router (200 body, 201 body, 204 vacío)
SAP devuelve 401 Re-autentica automáticamente y reintenta 1 vez
SAP devuelve 4xx/5xx Extrae error.message.value y lanza HTTPException(status_code, detail)
Timeout de conexión Lanza ConnectionError
Error de red Lanza ConnectionError
Reintento también falla Lanza HTTPException con el mensaje de error SAP
SAP no disponible HTTPException(500)

Otros proxies (fuera de Service Layer)

Proxy Módulo Sesión Consumido por
API Gateway (Crystal Reports, rs/v1/*) core/api_gateway.py (forward_to_api_gateway) requests.Session con cookies, global, se re-loguea en 401 igual que el Service Layer routers/apigateway.py
HANA (SQL directo) core/hana.py (hana_connection) Sin sesión persistente: abre y cierra una conexión hdbcli por request routers/reports.py

core/hana.py no pasa por forward_to_sapb1 ni maneja OData: se usa para reportes que necesitan una consulta SQL puntual contra HANA (por ejemplo, armar el detalle de una guía de remisión para imprimir stickers) en vez de paginar un recurso del Service Layer.